]>
git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
2 // $ phantomjs tests.js
5 var page
= require('webpage').create();
6 var url
= 'src/index.html';
7 var testMaxTime
= 5000;
9 page
.onResourceError = function(e
) {
10 console
.log("Error loading " + e
.url
);
15 console
.log("Failed");
19 function waitForGenerate(fn
, maxTime
) {
21 maxTime
= testMaxTime
;
23 var start
= new Date().getTime();
24 var prevAddressCount
= -1;
25 var wait
= function keepWaiting() {
26 var now
= new Date().getTime();
27 var hasTimedOut
= now
- start
> maxTime
;
28 var addressCount
= page
.evaluate(function() {
29 return $(".address").length
;
31 var hasFinished
= addressCount
> 0 && addressCount
== prevAddressCount
;
32 prevAddressCount
= addressCount
;
36 else if (hasTimedOut
) {
37 console
.log("Test timed out");
41 setTimeout(keepWaiting
, 100);
47 function waitForFeedback(fn
, maxTime
) {
49 maxTime
= testMaxTime
;
51 var start
= new Date().getTime();
52 var wait
= function keepWaiting() {
53 var now
= new Date().getTime();
54 var hasTimedOut
= now
- start
> maxTime
;
56 console
.log("Test timed out");
60 var feedback
= page
.evaluate(function() {
61 var feedback
= $(".feedback");
62 if (feedback
.css("display") == "none") {
65 return feedback
.text();
67 var hasFinished
= feedback
.length
> 0 && feedback
!= "Calculating...";
72 setTimeout(keepWaiting
, 100);
78 function waitForEntropyFeedback(fn
, maxTime
) {
80 maxTime
= testMaxTime
;
82 var origFeedback
= page
.evaluate(function() {
83 return $(".entropy-error").text();
85 var start
= new Date().getTime();
86 var wait
= function keepWaiting() {
87 var now
= new Date().getTime();
88 var hasTimedOut
= now
- start
> maxTime
;
90 console
.log("Test timed out");
94 var feedback
= page
.evaluate(function() {
95 var feedback
= $(".entropy-error");
96 if (feedback
.css("display") == "none") {
99 return feedback
.text();
101 var hasFinished
= feedback
!= origFeedback
;
106 setTimeout(keepWaiting
, 100);
113 if (tests
.length
> 0) {
114 var testsStr
= tests
.length
== 1 ? "test" : "tests";
115 console
.log(tests
.length
+ " " + testsStr
+ " remaining");
119 console
.log("Finished with 0 failures");
125 * Randomize array element order in-place.
126 * Using Durstenfeld shuffle algorithm.
127 * See http://stackoverflow.com/a/12646864
129 function shuffle(array
) {
130 for (var i
= array
.length
- 1; i
> 0; i
--) {
131 var j
= Math
.floor(Math
.random() * (i
+ 1));
141 // Page loads with status of 'success'
143 page
.open(url
, function(status
) {
144 if (status
!= "success") {
145 console
.log("Page did not load with status 'success'");
154 page
.open(url
, function(status
) {
155 var content
= page
.evaluate(function() {
156 return document
.body
.textContent
.trim();
159 console
.log("Page does not have text");
166 // Entering mnemonic generates addresses
168 page
.open(url
, function(status
) {
170 page
.evaluate(function() {
171 $(".phrase").val("abandon abandon ability").trigger("input");
174 waitForGenerate(function() {
175 var addressCount
= page
.evaluate(function() {
176 return $(".address").length
;
178 if (addressCount
!= 20) {
179 console
.log("Mnemonic did not generate addresses");
180 console
.log("Expected: " + expected
);
181 console
.log("Got: " + actual
);
189 // Random button generates random mnemonic
191 page
.open(url
, function(status
) {
192 // check initial phrase is empty
193 var phrase
= page
.evaluate(function() {
194 return $(".phrase").text();
197 console
.log("Initial phrase is not blank");
200 // press the 'generate' button
201 page
.evaluate(function() {
202 $(".generate").click();
204 // get the new phrase
205 waitForGenerate(function() {
206 var phrase
= page
.evaluate(function() {
207 return $(".phrase").val();
209 if (phrase
.length
<= 0) {
210 console
.log("Phrase not generated by pressing button");
218 // Mnemonic length can be customized
220 page
.open(url
, function(status
) {
221 // set the length to 6
222 var expectedLength
= "6";
223 page
.evaluate(function() {
224 $(".strength option[selected]").removeAttr("selected");
225 $(".strength option[value=6]").prop("selected", true);
227 // press the 'generate' button
228 page
.evaluate(function() {
229 $(".generate").click();
231 // check the new phrase is six words long
232 waitForGenerate(function() {
233 var actualLength
= page
.evaluate(function() {
234 var words
= $(".phrase").val().split(" ");
237 if (actualLength
!= expectedLength
) {
238 console
.log("Phrase not generated with correct length");
239 console
.log("Expected: " + expectedLength
);
240 console
.log("Actual: " + actualLength
);
248 // Passphrase can be set
250 page
.open(url
, function(status
) {
251 // set the phrase and passphrase
252 var expected
= "15pJzUWPGzR7avffV9nY5by4PSgSKG9rba";
253 page
.evaluate(function() {
254 $(".phrase").val("abandon abandon ability");
255 $(".passphrase").val("secure_passphrase").trigger("input");
257 // check the address is generated correctly
258 waitForGenerate(function() {
259 var actual
= page
.evaluate(function() {
260 return $(".address:first").text();
262 if (actual
!= expected
) {
263 console
.log("Passphrase results in wrong address");
264 console
.log("Expected: " + expected
);
265 console
.log("Actual: " + actual
);
273 // Network can be set to bitcoin testnet
275 page
.open(url
, function(status
) {
276 // set the phrase and coin
277 var expected
= "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi";
278 page
.evaluate(function() {
279 $(".phrase").val("abandon abandon ability");
280 $(".phrase").trigger("input");
281 $(".network option[selected]").removeAttr("selected");
282 $(".network option[value=1]").prop("selected", true);
283 $(".network").trigger("change");
285 // check the address is generated correctly
286 waitForGenerate(function() {
287 var actual
= page
.evaluate(function() {
288 return $(".address:first").text();
290 if (actual
!= expected
) {
291 console
.log("Bitcoin testnet address is incorrect");
292 console
.log("Expected: " + expected
);
293 console
.log("Actual: " + actual
);
301 // Network can be set to litecoin
303 page
.open(url
, function(status
) {
304 // set the phrase and coin
305 var expected
= "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
306 page
.evaluate(function() {
307 $(".phrase").val("abandon abandon ability");
308 $(".phrase").trigger("input");
309 $(".network option[selected]").removeAttr("selected");
310 $(".network option[value=2]").prop("selected", true);
311 $(".network").trigger("change");
313 // check the address is generated correctly
314 waitForGenerate(function() {
315 var actual
= page
.evaluate(function() {
316 return $(".address:first").text();
318 if (actual
!= expected
) {
319 console
.log("Litecoin address is incorrect");
320 console
.log("Expected: " + expected
);
321 console
.log("Actual: " + actual
);
329 // Network can be set to dogecoin
331 page
.open(url
, function(status
) {
332 // set the phrase and coin
333 var expected
= "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
334 page
.evaluate(function() {
335 $(".phrase").val("abandon abandon ability");
336 $(".phrase").trigger("input");
337 $(".network option[selected]").removeAttr("selected");
338 $(".network option[value=3]").prop("selected", true);
339 $(".network").trigger("change");
341 // check the address is generated correctly
342 waitForGenerate(function() {
343 var actual
= page
.evaluate(function() {
344 return $(".address:first").text();
346 if (actual
!= expected
) {
347 console
.log("Dogecoin address is incorrect");
348 console
.log("Expected: " + expected
);
349 console
.log("Actual: " + actual
);
357 // Network can be set to shadowcash
359 page
.open(url
, function(status
) {
360 // set the phrase and coin
361 var expected
= "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
362 page
.evaluate(function() {
363 $(".phrase").val("abandon abandon ability");
364 $(".phrase").trigger("input");
365 $(".network option[selected]").removeAttr("selected");
366 $(".network option[value=4]").prop("selected", true);
367 $(".network").trigger("change");
369 // check the address is generated correctly
370 waitForGenerate(function() {
371 var actual
= page
.evaluate(function() {
372 return $(".address:first").text();
374 if (actual
!= expected
) {
375 console
.log("Shadowcash address is incorrect");
376 console
.log("Expected: " + expected
);
377 console
.log("Actual: " + actual
);
385 // Network can be set to shadowcash testnet
387 page
.open(url
, function(status
) {
388 // set the phrase and coin
389 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
390 page
.evaluate(function() {
391 $(".phrase").val("abandon abandon ability");
392 $(".phrase").trigger("input");
393 $(".network option[selected]").removeAttr("selected");
394 $(".network option[value=5]").prop("selected", true);
395 $(".network").trigger("change");
397 // check the address is generated correctly
398 waitForGenerate(function() {
399 var actual
= page
.evaluate(function() {
400 return $(".address:first").text();
402 if (actual
!= expected
) {
403 console
.log("Shadowcash testnet address is incorrect");
404 console
.log("Expected: " + expected
);
405 console
.log("Actual: " + actual
);
413 // Network can be set to viacoin
415 page
.open(url
, function(status
) {
416 // set the phrase and coin
417 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
418 page
.evaluate(function() {
419 $(".phrase").val("abandon abandon ability");
420 $(".phrase").trigger("input");
421 $(".network option[selected]").removeAttr("selected");
422 $(".network option[value=6]").prop("selected", true);
423 $(".network").trigger("change");
425 // check the address is generated correctly
426 waitForGenerate(function() {
427 var actual
= page
.evaluate(function() {
428 return $(".address:first").text();
430 if (actual
!= expected
) {
431 console
.log("Viacoin address is incorrect");
432 console
.log("Expected: " + expected
);
433 console
.log("Actual: " + actual
);
441 // Network can be set to viacoin testnet
443 page
.open(url
, function(status
) {
444 // set the phrase and coin
445 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
446 page
.evaluate(function() {
447 $(".phrase").val("abandon abandon ability");
448 $(".phrase").trigger("input");
449 $(".network option[selected]").removeAttr("selected");
450 $(".network option[value=7]").prop("selected", true);
451 $(".network").trigger("change");
453 // check the address is generated correctly
454 waitForGenerate(function() {
455 var actual
= page
.evaluate(function() {
456 return $(".address:first").text();
458 if (actual
!= expected
) {
459 console
.log("Viacoin testnet address is incorrect");
460 console
.log("Expected: " + expected
);
461 console
.log("Actual: " + actual
);
469 // Network can be set to jumbucks
471 page
.open(url
, function(status
) {
472 // set the phrase and coin
473 var expected
= "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
474 page
.evaluate(function() {
475 $(".phrase").val("abandon abandon ability");
476 $(".phrase").trigger("input");
477 $(".network option[selected]").removeAttr("selected");
478 $(".network option[value=8]").prop("selected", true);
479 $(".network").trigger("change");
481 // check the address is generated correctly
482 waitForGenerate(function() {
483 var actual
= page
.evaluate(function() {
484 return $(".address:first").text();
486 if (actual
!= expected
) {
487 console
.log("Jumbucks address is incorrect");
488 console
.log("Expected: " + expected
);
489 console
.log("Actual: " + actual
);
497 // Network can be set to clam
499 page
.open(url
, function(status
) {
500 // set the phrase and coin
501 var expected
= "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
502 page
.evaluate(function() {
503 $(".phrase").val("abandon abandon ability");
504 $(".phrase").trigger("input");
505 $(".network option[selected]").removeAttr("selected");
506 $(".network option[value=9]").prop("selected", true);
507 $(".network").trigger("change");
509 // check the address is generated correctly
510 waitForGenerate(function() {
511 var actual
= page
.evaluate(function() {
512 return $(".address:first").text();
514 if (actual
!= expected
) {
515 console
.log("CLAM address is incorrect");
516 console
.log("Expected: " + expected
);
517 console
.log("Actual: " + actual
);
525 // Network can be set to dash
527 page
.open(url
, function(status
) {
528 // set the phrase and coin
529 var expected
= "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
530 page
.evaluate(function() {
531 $(".phrase").val("abandon abandon ability");
532 $(".phrase").trigger("input");
533 $(".network option[selected]").removeAttr("selected");
534 $(".network option[value=10]").prop("selected", true);
535 $(".network").trigger("change");
537 // check the address is generated correctly
538 waitForGenerate(function() {
539 var actual
= page
.evaluate(function() {
540 return $(".address:first").text();
542 if (actual
!= expected
) {
543 console
.log("DASH address is incorrect");
544 console
.log("Expected: " + expected
);
545 console
.log("Actual: " + actual
);
553 // Network can be set to namecoin
555 page
.open(url
, function(status
) {
556 // set the phrase and coin
557 var expected
= "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
558 page
.evaluate(function() {
559 $(".phrase").val("abandon abandon ability");
560 $(".phrase").trigger("input");
561 $(".network option[selected]").removeAttr("selected");
562 $(".network option[value=11]").prop("selected", true);
563 $(".network").trigger("change");
565 // check the address is generated correctly
566 waitForGenerate(function() {
567 var actual
= page
.evaluate(function() {
568 return $(".address:first").text();
570 if (actual
!= expected
) {
571 console
.log("Namecoin address is incorrect");
572 console
.log("Expected: " + expected
);
573 console
.log("Actual: " + actual
);
581 // Network can be set to peercoin
583 page
.open(url
, function(status
) {
584 // set the phrase and coin
585 var expected
= "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
586 page
.evaluate(function() {
587 $(".phrase").val("abandon abandon ability");
588 $(".phrase").trigger("input");
589 $(".network option[selected]").removeAttr("selected");
590 $(".network option[value=12]").prop("selected", true);
591 $(".network").trigger("change");
593 // check the address is generated correctly
594 waitForGenerate(function() {
595 var actual
= page
.evaluate(function() {
596 return $(".address:first").text();
598 if (actual
!= expected
) {
599 console
.log("Peercoin address is incorrect");
600 console
.log("Expected: " + expected
);
601 console
.log("Actual: " + actual
);
609 // BIP39 seed is set from phrase
611 page
.open(url
, function(status
) {
613 var expected
= "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
614 page
.evaluate(function() {
615 $(".phrase").val("abandon abandon ability");
616 $(".phrase").trigger("input");
618 // check the address is generated correctly
619 waitForGenerate(function() {
620 var actual
= page
.evaluate(function() {
621 return $(".seed").val();
623 if (actual
!= expected
) {
624 console
.log("BIP39 seed is incorrectly generated from mnemonic");
625 console
.log("Expected: " + expected
);
626 console
.log("Actual: " + actual
);
634 // BIP32 root key is set from phrase
636 page
.open(url
, function(status
) {
638 var expected
= "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
639 page
.evaluate(function() {
640 $(".phrase").val("abandon abandon ability");
641 $(".phrase").trigger("input");
643 // check the address is generated correctly
644 waitForGenerate(function() {
645 var actual
= page
.evaluate(function() {
646 return $(".root-key").val();
648 if (actual
!= expected
) {
649 console
.log("Root key is incorrectly generated from mnemonic");
650 console
.log("Expected: " + expected
);
651 console
.log("Actual: " + actual
);
659 // Tabs show correct addresses when changed
661 page
.open(url
, function(status
) {
663 var expected
= "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
664 page
.evaluate(function() {
665 $(".phrase").val("abandon abandon ability");
666 $(".phrase").trigger("input");
669 waitForGenerate(function() {
670 page
.evaluate(function() {
671 $("#bip32-tab a").click();
673 // check the address is generated correctly
674 waitForGenerate(function() {
675 var actual
= page
.evaluate(function() {
676 return $(".address:first").text();
678 if (actual
!= expected
) {
679 console
.log("Clicking tab generates incorrect address");
680 console
.log("Expected: " + expected
);
681 console
.log("Actual: " + actual
);
690 // BIP44 derivation path is shown
692 page
.open(url
, function(status
) {
694 var expected
= "m/44'/0'/0'/0";
695 page
.evaluate(function() {
696 $(".phrase").val("abandon abandon ability");
697 $(".phrase").trigger("input");
699 // check the derivation path of the first address
700 waitForGenerate(function() {
701 var actual
= page
.evaluate(function() {
702 return $("#bip44 .path").val();
704 if (actual
!= expected
) {
705 console
.log("BIP44 derivation path is incorrect");
706 console
.log("Expected: " + expected
);
707 console
.log("Actual: " + actual
);
715 // BIP44 extended private key is shown
717 page
.open(url
, function(status
) {
719 var expected
= "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
720 page
.evaluate(function() {
721 $(".phrase").val("abandon abandon ability");
722 $(".phrase").trigger("input");
724 // check the BIP44 extended private key
725 waitForGenerate(function() {
726 var actual
= page
.evaluate(function() {
727 return $(".extended-priv-key").val();
729 if (actual
!= expected
) {
730 console
.log("BIP44 extended private key is incorrect");
731 console
.log("Expected: " + expected
);
732 console
.log("Actual: " + actual
);
740 // BIP44 extended public key is shown
742 page
.open(url
, function(status
) {
744 var expected
= "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
745 page
.evaluate(function() {
746 $(".phrase").val("abandon abandon ability");
747 $(".phrase").trigger("input");
749 // check the BIP44 extended public key
750 waitForGenerate(function() {
751 var actual
= page
.evaluate(function() {
752 return $(".extended-pub-key").val();
754 if (actual
!= expected
) {
755 console
.log("BIP44 extended public key is incorrect");
756 console
.log("Expected: " + expected
);
757 console
.log("Actual: " + actual
);
765 // BIP44 purpose field changes address list
767 page
.open(url
, function(status
) {
769 var expected
= "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
770 page
.evaluate(function() {
771 $(".phrase").val("abandon abandon ability");
772 $(".phrase").trigger("input");
774 waitForGenerate(function() {
775 // change the bip44 purpose field to 45
776 page
.evaluate(function() {
777 $("#bip44 .purpose").val("45");
778 $("#bip44 .purpose").trigger("input");
780 waitForGenerate(function() {
781 // check the address for the new derivation path
782 var actual
= page
.evaluate(function() {
783 return $(".address:first").text();
785 if (actual
!= expected
) {
786 console
.log("BIP44 purpose field generates incorrect address");
787 console
.log("Expected: " + expected
);
788 console
.log("Actual: " + actual
);
797 // BIP44 coin field changes address list
799 page
.open(url
, function(status
) {
801 var expected
= "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
802 page
.evaluate(function() {
803 $(".phrase").val("abandon abandon ability");
804 $(".phrase").trigger("input");
806 waitForGenerate(function() {
807 // change the bip44 purpose field to 45
808 page
.evaluate(function() {
809 $("#bip44 .coin").val("1");
810 $("#bip44 .coin").trigger("input");
812 waitForGenerate(function() {
813 // check the address for the new derivation path
814 var actual
= page
.evaluate(function() {
815 return $(".address:first").text();
817 if (actual
!= expected
) {
818 console
.log("BIP44 coin field generates incorrect address");
819 console
.log("Expected: " + expected
);
820 console
.log("Actual: " + actual
);
829 // BIP44 account field changes address list
831 page
.open(url
, function(status
) {
833 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
834 page
.evaluate(function() {
835 $(".phrase").val("abandon abandon ability");
836 $(".phrase").trigger("input");
838 waitForGenerate(function() {
839 // change the bip44 purpose field to 45
840 page
.evaluate(function() {
841 $("#bip44 .account").val("1");
842 $("#bip44 .account").trigger("input");
844 waitForGenerate(function() {
845 // check the address for the new derivation path
846 var actual
= page
.evaluate(function() {
847 return $(".address:first").text();
849 if (actual
!= expected
) {
850 console
.log("BIP44 account field generates incorrect address");
851 console
.log("Expected: " + expected
);
852 console
.log("Actual: " + actual
);
861 // BIP44 change field changes address list
863 page
.open(url
, function(status
) {
865 var expected
= "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
866 page
.evaluate(function() {
867 $(".phrase").val("abandon abandon ability");
868 $(".phrase").trigger("input");
870 waitForGenerate(function() {
871 // change the bip44 purpose field to 45
872 page
.evaluate(function() {
873 $("#bip44 .change").val("1");
874 $("#bip44 .change").trigger("input");
876 waitForGenerate(function() {
877 // check the address for the new derivation path
878 var actual
= page
.evaluate(function() {
879 return $(".address:first").text();
881 if (actual
!= expected
) {
882 console
.log("BIP44 change field generates incorrect address");
883 console
.log("Expected: " + expected
);
884 console
.log("Actual: " + actual
);
893 // BIP32 derivation path can be set
895 page
.open(url
, function(status
) {
897 var expected
= "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
898 page
.evaluate(function() {
899 $(".phrase").val("abandon abandon ability");
900 $(".phrase").trigger("input");
903 waitForGenerate(function() {
904 page
.evaluate(function() {
905 $("#bip32-tab a").click();
907 // set the derivation path to m/1
908 waitForGenerate(function() {
909 page
.evaluate(function() {
910 $("#bip32 .path").val("m/1");
911 $("#bip32 .path").trigger("input");
913 // check the address is generated correctly
914 waitForGenerate(function() {
915 var actual
= page
.evaluate(function() {
916 return $(".address:first").text();
918 if (actual
!= expected
) {
919 console
.log("Custom BIP32 path generates incorrect address");
920 console
.log("Expected: " + expected
);
921 console
.log("Actual: " + actual
);
931 // BIP32 can use hardened derivation paths
933 page
.open(url
, function(status
) {
935 var expected
= "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
936 page
.evaluate(function() {
937 $(".phrase").val("abandon abandon ability");
938 $(".phrase").trigger("input");
941 waitForGenerate(function() {
942 page
.evaluate(function() {
943 $("#bip32-tab a").click();
945 // set the derivation path to m/0'
946 waitForGenerate(function() {
947 page
.evaluate(function() {
948 $("#bip32 .path").val("m/0'");
949 $("#bip32 .path").trigger("input");
951 // check the address is generated correctly
952 waitForGenerate(function() {
953 var actual
= page
.evaluate(function() {
954 return $(".address:first").text();
956 if (actual
!= expected
) {
957 console
.log("Hardened BIP32 path generates incorrect address");
958 console
.log("Expected: " + expected
);
959 console
.log("Actual: " + actual
);
969 // BIP32 extended private key is shown
971 page
.open(url
, function(status
) {
973 var expected
= "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
974 page
.evaluate(function() {
975 $(".phrase").val("abandon abandon ability");
976 $(".phrase").trigger("input");
979 waitForGenerate(function() {
980 page
.evaluate(function() {
981 $("#bip32-tab a").click();
983 // check the extended private key is generated correctly
984 waitForGenerate(function() {
985 var actual
= page
.evaluate(function() {
986 return $(".extended-priv-key").val();
988 if (actual
!= expected
) {
989 console
.log("BIP32 extended private key is incorrect");
990 console
.log("Expected: " + expected
);
991 console
.log("Actual: " + actual
);
1000 // BIP32 extended public key is shown
1002 page
.open(url
, function(status
) {
1004 var expected
= "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1005 page
.evaluate(function() {
1006 $(".phrase").val("abandon abandon ability");
1007 $(".phrase").trigger("input");
1010 waitForGenerate(function() {
1011 page
.evaluate(function() {
1012 $("#bip32-tab a").click();
1014 // check the extended public key is generated correctly
1015 waitForGenerate(function() {
1016 var actual
= page
.evaluate(function() {
1017 return $(".extended-pub-key").val();
1019 if (actual
!= expected
) {
1020 console
.log("BIP32 extended public key is incorrect");
1021 console
.log("Expected: " + expected
);
1022 console
.log("Actual: " + actual
);
1031 // Derivation path is shown in table
1033 page
.open(url
, function(status
) {
1035 var expected
= "m/44'/0'/0'/0/0";
1036 page
.evaluate(function() {
1037 $(".phrase").val("abandon abandon ability");
1038 $(".phrase").trigger("input");
1040 // check for derivation path in table
1041 waitForGenerate(function() {
1042 var actual
= page
.evaluate(function() {
1043 return $(".index:first").text();
1045 if (actual
!= expected
) {
1046 console
.log("Derivation path shown incorrectly in table");
1047 console
.log("Expected: " + expected
);
1048 console
.log("Actual: " + actual
);
1056 // Derivation path for address can be hardened
1058 page
.open(url
, function(status
) {
1060 var expected
= "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1061 page
.evaluate(function() {
1062 $(".phrase").val("abandon abandon ability");
1063 $(".phrase").trigger("input");
1066 waitForGenerate(function() {
1067 page
.evaluate(function() {
1068 $("#bip32-tab a").click();
1070 waitForGenerate(function() {
1071 // select the hardened addresses option
1072 page
.evaluate(function() {
1073 $(".hardened-addresses").prop("checked", true);
1074 $(".hardened-addresses").trigger("change");
1076 waitForGenerate(function() {
1077 // check the generated address is hardened
1078 var actual
= page
.evaluate(function() {
1079 return $(".address:first").text();
1081 if (actual
!= expected
) {
1082 console
.log("Hardened address is incorrect");
1083 console
.log("Expected: " + expected
);
1084 console
.log("Actual: " + actual
);
1094 // Derivation path visibility can be toggled
1096 page
.open(url
, function(status
) {
1098 page
.evaluate(function() {
1099 $(".phrase").val("abandon abandon ability");
1100 $(".phrase").trigger("input");
1102 waitForGenerate(function() {
1103 // toggle path visibility
1104 page
.evaluate(function() {
1105 $(".index-toggle").click();
1107 // check the path is not visible
1108 var isInvisible
= page
.evaluate(function() {
1109 return $(".index:first span").hasClass("invisible");
1112 console
.log("Toggled derivation path is visible");
1122 page
.open(url
, function(status
) {
1123 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1125 page
.evaluate(function() {
1126 $(".phrase").val("abandon abandon ability").trigger("input");
1129 waitForGenerate(function() {
1130 var actual
= page
.evaluate(function() {
1131 return $(".address:first").text();
1133 if (actual
!= expected
) {
1134 console
.log("Address is not shown");
1135 console
.log("Expected: " + expected
);
1136 console
.log("Got: " + actual
);
1144 // Addresses are shown in order of derivation path
1146 page
.open(url
, function(status
) {
1148 page
.evaluate(function() {
1149 $(".phrase").val("abandon abandon ability").trigger("input");
1151 // get the derivation paths
1152 waitForGenerate(function() {
1153 var paths
= page
.evaluate(function() {
1154 return $(".index").map(function(i
, e
) {
1158 if (paths
.length
!= 20) {
1159 console
.log("Total paths is less than expected: " + paths
.length
);
1162 for (var i
=0; i
<paths
.length
; i
++) {
1163 var expected
= "m/44'/0'/0'/0/" + i
;
1164 var actual
= paths
[i
];
1165 if (actual
!= expected
) {
1166 console
.log("Path " + i
+ " is incorrect");
1167 console
.log("Expected: " + expected
);
1168 console
.log("Actual: " + actual
);
1177 // Address visibility can be toggled
1179 page
.open(url
, function(status
) {
1181 page
.evaluate(function() {
1182 $(".phrase").val("abandon abandon ability");
1183 $(".phrase").trigger("input");
1185 waitForGenerate(function() {
1186 // toggle address visibility
1187 page
.evaluate(function() {
1188 $(".address-toggle").click();
1190 // check the address is not visible
1191 var isInvisible
= page
.evaluate(function() {
1192 return $(".address:first span").hasClass("invisible");
1195 console
.log("Toggled address is visible");
1203 // Public key is shown
1205 page
.open(url
, function(status
) {
1206 var expected
= "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1208 page
.evaluate(function() {
1209 $(".phrase").val("abandon abandon ability").trigger("input");
1212 waitForGenerate(function() {
1213 var actual
= page
.evaluate(function() {
1214 return $(".pubkey:first").text();
1216 if (actual
!= expected
) {
1217 console
.log("Public key is not shown");
1218 console
.log("Expected: " + expected
);
1219 console
.log("Got: " + actual
);
1227 // Public key visibility can be toggled
1229 page
.open(url
, function(status
) {
1231 page
.evaluate(function() {
1232 $(".phrase").val("abandon abandon ability");
1233 $(".phrase").trigger("input");
1235 waitForGenerate(function() {
1236 // toggle public key visibility
1237 page
.evaluate(function() {
1238 $(".public-key-toggle").click();
1240 // check the public key is not visible
1241 var isInvisible
= page
.evaluate(function() {
1242 return $(".pubkey:first span").hasClass("invisible");
1245 console
.log("Toggled public key is visible");
1253 // Private key is shown
1255 page
.open(url
, function(status
) {
1256 var expected
= "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1258 page
.evaluate(function() {
1259 $(".phrase").val("abandon abandon ability").trigger("input");
1262 waitForGenerate(function() {
1263 var actual
= page
.evaluate(function() {
1264 return $(".privkey:first").text();
1266 if (actual
!= expected
) {
1267 console
.log("Private key is not shown");
1268 console
.log("Expected: " + expected
);
1269 console
.log("Got: " + actual
);
1277 // Private key visibility can be toggled
1279 page
.open(url
, function(status
) {
1281 page
.evaluate(function() {
1282 $(".phrase").val("abandon abandon ability");
1283 $(".phrase").trigger("input");
1285 waitForGenerate(function() {
1286 // toggle private key visibility
1287 page
.evaluate(function() {
1288 $(".private-key-toggle").click();
1290 // check the private key is not visible
1291 var isInvisible
= page
.evaluate(function() {
1292 return $(".privkey:first span").hasClass("invisible");
1295 console
.log("Toggled private key is visible");
1303 // More addresses can be generated
1305 page
.open(url
, function(status
) {
1307 page
.evaluate(function() {
1308 $(".phrase").val("abandon abandon ability");
1309 $(".phrase").trigger("input");
1311 waitForGenerate(function() {
1312 // generate more addresses
1313 page
.evaluate(function() {
1316 waitForGenerate(function() {
1317 // check there are more addresses
1318 var addressCount
= page
.evaluate(function() {
1319 return $(".address").length
;
1321 if (addressCount
!= 40) {
1322 console
.log("More addresses cannot be generated");
1331 // A custom number of additional addresses can be generated
1333 page
.open(url
, function(status
) {
1335 page
.evaluate(function() {
1336 $(".phrase").val("abandon abandon ability");
1337 $(".phrase").trigger("input");
1339 waitForGenerate(function() {
1340 // get the current number of addresses
1341 var oldAddressCount
= page
.evaluate(function() {
1342 return $(".address").length
;
1344 // set a custom number of additional addresses
1345 page
.evaluate(function() {
1346 $(".rows-to-add").val(1);
1348 // generate more addresses
1349 page
.evaluate(function() {
1352 waitForGenerate(function() {
1353 // check there are the correct number of addresses
1354 var newAddressCount
= page
.evaluate(function() {
1355 return $(".address").length
;
1357 if (newAddressCount
- oldAddressCount
!= 1) {
1358 console
.log("Number of additional addresses cannot be customized");
1359 console
.log(newAddressCount
)
1360 console
.log(oldAddressCount
)
1369 // Additional addresses are shown in order of derivation path
1371 page
.open(url
, function(status
) {
1373 page
.evaluate(function() {
1374 $(".phrase").val("abandon abandon ability").trigger("input");
1376 waitForGenerate(function() {
1377 // generate more addresses
1378 page
.evaluate(function() {
1381 // get the derivation paths
1382 waitForGenerate(function() {
1383 var paths
= page
.evaluate(function() {
1384 return $(".index").map(function(i
, e
) {
1388 if (paths
.length
!= 40) {
1389 console
.log("Total additional paths is less than expected: " + paths
.length
);
1392 for (var i
=0; i
<paths
.length
; i
++) {
1393 var expected
= "m/44'/0'/0'/0/" + i
;
1394 var actual
= paths
[i
];
1395 if (actual
!= expected
) {
1396 console
.log("Path " + i
+ " is not in correct order");
1397 console
.log("Expected: " + expected
);
1398 console
.log("Actual: " + actual
);
1408 // BIP32 root key can be set by the user
1410 page
.open(url
, function(status
) {
1411 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1413 page
.evaluate(function() {
1414 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1416 waitForGenerate(function() {
1417 var actual
= page
.evaluate(function() {
1418 return $(".address:first").text();
1420 if (actual
!= expected
) {
1421 console
.log("Setting BIP32 root key results in wrong address");
1422 console
.log("Expected: " + expected
);
1423 console
.log("Actual: " + actual
);
1431 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1433 page
.open(url
, function(status
) {
1436 page
.evaluate(function() {
1437 $(".phrase").val("A non-blank but invalid value");
1439 // Accept any confirm dialogs
1440 page
.onConfirm = function() {
1444 page
.evaluate(function() {
1445 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1447 waitForGenerate(function() {
1448 var actual
= page
.evaluate(function() {
1449 return $(".phrase").val();
1451 if (actual
!= expected
) {
1452 console
.log("Phrase not cleared when setting BIP32 root key");
1453 console
.log("Expected: " + expected
);
1454 console
.log("Actual: " + actual
);
1462 // Clearing of phrase, passphrase and seed can be cancelled by user
1464 page
.open(url
, function(status
) {
1465 var expected
= "abandon abandon ability";
1467 page
.evaluate(function() {
1468 $(".phrase").val("abandon abandon ability");
1470 // Cancel any confirm dialogs
1471 page
.onConfirm = function() {
1475 page
.evaluate(function() {
1476 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1478 var actual
= page
.evaluate(function() {
1479 return $(".phrase").val();
1481 if (actual
!= expected
) {
1482 console
.log("Phrase not retained when cancelling changes to BIP32 root key");
1483 console
.log("Expected: " + expected
);
1484 console
.log("Actual: " + actual
);
1491 // Custom BIP32 root key is used when changing the derivation path
1493 page
.open(url
, function(status
) {
1494 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1496 page
.evaluate(function() {
1497 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1499 waitForGenerate(function() {
1500 // change the derivation path
1501 page
.evaluate(function() {
1502 $("#account").val("1").trigger("input");
1504 // check the bip32 root key is used for derivation, not the blank phrase
1505 waitForGenerate(function() {
1506 var actual
= page
.evaluate(function() {
1507 return $(".address:first").text();
1509 if (actual
!= expected
) {
1510 console
.log("Changing the derivation path does not use BIP32 root key");
1511 console
.log("Expected: " + expected
);
1512 console
.log("Actual: " + actual
);
1521 // Incorrect mnemonic shows error
1523 page
.open(url
, function(status
) {
1525 page
.evaluate(function() {
1526 $(".phrase").val("abandon abandon abandon").trigger("input");
1528 waitForFeedback(function() {
1529 // check there is an error shown
1530 var feedback
= page
.evaluate(function() {
1531 return $(".feedback").text();
1533 if (feedback
.length
<= 0) {
1534 console
.log("Invalid mnemonic does not show error");
1542 // Incorrect word shows suggested replacement
1544 page
.open(url
, function(status
) {
1546 page
.evaluate(function() {
1547 $(".phrase").val("abandon abandon abiliti").trigger("input");
1549 // check there is a suggestion shown
1550 waitForFeedback(function() {
1551 var feedback
= page
.evaluate(function() {
1552 return $(".feedback").text();
1554 if (feedback
.indexOf("did you mean ability?") < 0) {
1555 console
.log("Incorrect word does not show suggested replacement");
1556 console
.log("Error: " + error
);
1564 // Incorrect BIP32 root key shows error
1566 page
.open(url
, function(status
) {
1568 page
.evaluate(function() {
1569 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1571 // check there is an error shown
1572 waitForFeedback(function() {
1573 var feedback
= page
.evaluate(function() {
1574 return $(".feedback").text();
1576 if (feedback
!= "Invalid root key") {
1577 console
.log("Invalid root key does not show error");
1578 console
.log("Error: " + error
);
1586 // Derivation path not starting with m shows error
1588 page
.open(url
, function(status
) {
1589 // set the mnemonic phrase
1590 page
.evaluate(function() {
1591 $(".phrase").val("abandon abandon ability").trigger("input");
1593 waitForGenerate(function() {
1594 // select the bip32 tab so custom derivation path can be set
1595 page
.evaluate(function() {
1596 $("#bip32-tab a").click();
1598 waitForGenerate(function() {
1599 // set the incorrect derivation path
1600 page
.evaluate(function() {
1601 $("#bip32 .path").val("n/0").trigger("input");
1603 waitForFeedback(function() {
1604 var feedback
= page
.evaluate(function() {
1605 return $(".feedback").text();
1607 if (feedback
!= "First character must be 'm'") {
1608 console
.log("Derivation path not starting with m should show error");
1609 console
.log("Error: " + error
);
1619 // Derivation path containing invalid characters shows useful error
1621 page
.open(url
, function(status
) {
1622 // set the mnemonic phrase
1623 page
.evaluate(function() {
1624 $(".phrase").val("abandon abandon ability").trigger("input");
1626 waitForGenerate(function() {
1627 // select the bip32 tab so custom derivation path can be set
1628 page
.evaluate(function() {
1629 $("#bip32-tab a").click();
1631 waitForGenerate(function() {
1632 // set the incorrect derivation path
1633 page
.evaluate(function() {
1634 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1636 waitForFeedback(function() {
1637 var feedback
= page
.evaluate(function() {
1638 return $(".feedback").text();
1640 if (feedback
!= "Invalid characters 0wrong1 found at depth 2") {
1641 console
.log("Derivation path with invalid characters should show error");
1642 console
.log("Error: " + error
);
1652 // Github Issue 11: Default word length is 15
1653 // https://github.com/iancoleman/bip39/issues/11
1655 page
.open(url
, function(status
) {
1656 // get the word length
1657 var defaultLength
= page
.evaluate(function() {
1658 return $(".strength").val();
1660 if (defaultLength
!= 15) {
1661 console
.log("Default word length is not 15");
1669 // Github Issue 12: Generate more rows with private keys hidden
1670 // https://github.com/iancoleman/bip39/issues/12
1672 page
.open(url
, function(status
) {
1674 page
.evaluate(function() {
1675 $(".phrase").val("abandon abandon ability");
1676 $(".phrase").trigger("input");
1678 waitForGenerate(function() {
1679 // toggle private keys hidden, then generate more addresses
1680 page
.evaluate(function() {
1681 $(".private-key-toggle").click();
1684 waitForGenerate(function() {
1685 // check more have been generated
1687 var numPrivKeys
= page
.evaluate(function() {
1688 return $(".privkey").length
;
1690 if (numPrivKeys
!= expected
) {
1691 console
.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1692 console
.log("Expected: " + expected
);
1693 console
.log("Actual: " + numPrivKeys
);
1696 // check no private keys are shown
1697 var numHiddenPrivKeys
= page
.evaluate(function() {
1698 return $(".privkey span[class=invisible]").length
;
1700 if (numHiddenPrivKeys
!= expected
) {
1701 console
.log("Generating more does not retain hidden state of privkeys");
1702 console
.log("Expected: " + expected
);
1703 console
.log("Actual: " + numHiddenPrivKeys
);
1712 // Github Issue 19: Mnemonic is not sensitive to whitespace
1713 // https://github.com/iancoleman/bip39/issues/19
1715 page
.open(url
, function(status
) {
1717 var expected
= "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1718 page
.evaluate(function() {
1719 var doubleSpace
= " ";
1720 $(".phrase").val("urge cat" + doubleSpace
+ "bid");
1721 $(".phrase").trigger("input");
1723 waitForGenerate(function() {
1724 // Check the bip32 root key is correct
1725 var actual
= page
.evaluate(function() {
1726 return $(".root-key").val();
1728 if (actual
!= expected
) {
1729 console
.log("Mnemonic is sensitive to whitespace");
1730 console
.log("Expected: " + expected
);
1731 console
.log("Actual: " + actual
);
1739 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1740 // https://github.com/iancoleman/bip39/issues/23
1742 page
.open(url
, function(status
) {
1743 // 1) and 2) set the phrase
1744 page
.evaluate(function() {
1745 $(".phrase").val("abandon abandon ability").trigger("input");
1747 waitForGenerate(function() {
1748 // 3) select bip32 tab
1749 page
.evaluate(function() {
1750 $("#bip32-tab a").click();
1752 waitForGenerate(function() {
1753 // 4) switch from bitcoin to litecoin
1754 page
.evaluate(function() {
1755 $(".network").val("2").trigger("change");
1757 waitForGenerate(function() {
1758 // 5) Check derivation path is displayed correctly
1759 var expected
= "m/0/0";
1760 var actual
= page
.evaluate(function() {
1761 return $(".index:first").text();
1763 if (actual
!= expected
) {
1764 console
.log("Github Issue 23 Part 1: derivation path display error");
1765 console
.log("Expected: " + expected
);
1766 console
.log("Actual: " + actual
);
1769 // 5) Check address is displayed correctly
1770 var expected
= "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1771 var actual
= page
.evaluate(function() {
1772 return $(".address:first").text();
1774 if (actual
!= expected
) {
1775 console
.log("Github Issue 23 Part 1: address display error");
1776 console
.log("Expected: " + expected
);
1777 console
.log("Actual: " + actual
);
1787 // Github Issue 23 Part 2: Coin selection in derivation path
1788 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1790 page
.open(url
, function(status
) {
1792 page
.evaluate(function() {
1793 $(".phrase").val("abandon abandon ability").trigger("input");
1795 waitForGenerate(function() {
1796 // switch from bitcoin to clam
1797 page
.evaluate(function() {
1798 $(".network").val("9").trigger("change");
1800 waitForGenerate(function() {
1801 // check derivation path is displayed correctly
1802 var expected
= "m/44'/23'/0'/0/0";
1803 var actual
= page
.evaluate(function() {
1804 return $(".index:first").text();
1806 if (actual
!= expected
) {
1807 console
.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1808 console
.log("Expected: " + expected
);
1809 console
.log("Actual: " + actual
);
1818 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1819 // https://github.com/iancoleman/bip39/issues/26
1821 page
.open(url
, function(status
) {
1822 // 1) 2) and 3) set the root key
1823 page
.evaluate(function() {
1824 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1826 waitForGenerate(function() {
1827 // 4) switch from bitcoin to viacoin
1828 page
.evaluate(function() {
1829 $(".network").val("6").trigger("change");
1831 waitForGenerate(function() {
1832 // 5) ensure the derived address is correct
1833 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1834 var actual
= page
.evaluate(function() {
1835 return $(".address:first").text();
1837 if (actual
!= expected
) {
1838 console
.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1839 console
.log("Expected: " + expected
);
1840 console
.log("Actual: " + actual
);
1849 // Selecting a language with no existing phrase should generate a phrase in
1852 page
.open(url
, function(status
) {
1853 // Select a language
1854 // Need to manually simulate hash being set due to quirk between
1855 // 'click' event triggered by javascript vs triggered by mouse.
1856 // Perhaps look into page.sendEvent
1857 // http://phantomjs.org/api/webpage/method/send-event.html
1858 page
.evaluate(function() {
1859 window
.location
.hash
= "#japanese";
1860 $("a[href='#japanese']").trigger("click");
1862 waitForGenerate(function() {
1863 // Check the mnemonic is in Japanese
1864 var phrase
= page
.evaluate(function() {
1865 return $(".phrase").val();
1867 if (phrase
.length
<= 0) {
1868 console
.log("No Japanese phrase generated");
1871 if (phrase
.charCodeAt(0) < 128) {
1872 console
.log("First character of Japanese phrase is ascii");
1873 console
.log("Phrase: " + phrase
);
1881 // Selecting a language with existing phrase should update the phrase to use
1884 page
.open(url
, function(status
) {
1885 // Set the phrase to an English phrase.
1886 page
.evaluate(function() {
1887 $(".phrase").val("abandon abandon ability").trigger("input");
1889 waitForGenerate(function() {
1890 // Change to Italian
1891 // Need to manually simulate hash being set due to quirk between
1892 // 'click' event triggered by javascript vs triggered by mouse.
1893 // Perhaps look into page.sendEvent
1894 // http://phantomjs.org/api/webpage/method/send-event.html
1895 page
.evaluate(function() {
1896 window
.location
.hash
= "#italian";
1897 $("a[href='#italian']").trigger("click");
1899 waitForGenerate(function() {
1900 // Check only the language changes, not the phrase
1901 var expected
= "abaco abaco abbaglio";
1902 var actual
= page
.evaluate(function() {
1903 return $(".phrase").val();
1905 if (actual
!= expected
) {
1906 console
.log("Changing language with existing phrase");
1907 console
.log("Expected: " + expected
);
1908 console
.log("Actual: " + actual
);
1911 // Check the address is correct
1912 var expected
= "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1913 var actual
= page
.evaluate(function() {
1914 return $(".address:first").text();
1916 if (actual
!= expected
) {
1917 console
.log("Changing language generates incorrect address");
1918 console
.log("Expected: " + expected
);
1919 console
.log("Actual: " + actual
);
1928 // Suggested replacement for erroneous word in non-English language
1930 page
.open(url
, function(status
) {
1931 // Set an incorrect phrase in Italian
1932 page
.evaluate(function() {
1933 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
1935 waitForFeedback(function() {
1936 // Check the suggestion is correct
1937 var feedback
= page
.evaluate(function() {
1938 return $(".feedback").text();
1940 if (feedback
.indexOf("did you mean abbaglio?") < 0) {
1941 console
.log("Incorrect Italian word does not show suggested replacement");
1942 console
.log("Error: " + error
);
1951 // Japanese word does not break across lines.
1953 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1955 page
.open(url
, function(status
) {
1956 hasWordBreakCss
= page
.content
.indexOf("word-break: keep-all;") > -1;
1957 if (!hasWordBreakCss
) {
1958 console
.log("Japanese words can break across lines mid-word");
1959 console
.log("Check CSS for '.phrase { word-break: keep-all; }'");
1962 // Run the next test
1967 // Language can be specified at page load using hash value in url
1969 page
.open(url
, function(status
) {
1970 // Set the page hash as if it were on a fresh page load
1971 page
.evaluate(function() {
1972 window
.location
.hash
= "#japanese";
1974 // Generate a random phrase
1975 page
.evaluate(function() {
1976 $(".generate").trigger("click");
1978 waitForGenerate(function() {
1979 // Check the phrase is in Japanese
1980 var phrase
= page
.evaluate(function() {
1981 return $(".phrase").val();
1983 if (phrase
.length
<= 0) {
1984 console
.log("No phrase generated using url hash");
1987 if (phrase
.charCodeAt(0) < 128) {
1988 console
.log("Language not detected from url hash on page load.");
1989 console
.log("Phrase: " + phrase
);
1997 // Entropy unit tests
1999 page
.open(url
, function(status
) {
2000 var response
= page
.evaluate(function() {
2002 // binary entropy is detected
2004 e
= Entropy
.fromString("01010101");
2005 if (e
.base
.str
!= "binary") {
2006 return "Binary entropy not detected correctly";
2012 // base6 entropy is detected
2014 e
= Entropy
.fromString("012345012345");
2015 if (e
.base
.str
!= "base 6") {
2016 return "base6 entropy not detected correctly";
2022 // dice entropy is detected
2024 e
= Entropy
.fromString("123456123456");
2025 if (e
.base
.str
!= "base 6 (dice)") {
2026 return "dice entropy not detected correctly";
2032 // base10 entropy is detected
2034 e
= Entropy
.fromString("0123456789");
2035 if (e
.base
.str
!= "base 10") {
2036 return "base10 entropy not detected correctly";
2042 // hex entropy is detected
2044 e
= Entropy
.fromString("0123456789ABCDEF");
2045 if (e
.base
.str
!= "hexadecimal") {
2046 return "hexadecimal entropy not detected correctly";
2052 // card entropy is detected
2054 e
= Entropy
.fromString("AC4DTHKS");
2055 if (e
.base
.str
!= "card") {
2056 return "card entropy not detected correctly";
2062 // entropy is case insensitive
2064 e
= Entropy
.fromString("aBcDeF");
2065 if (e
.cleanStr
!= "aBcDeF") {
2066 return "Entropy should not be case sensitive";
2072 // dice entropy is converted to base6
2074 e
= Entropy
.fromString("123456");
2075 if (e
.cleanStr
!= "123450") {
2076 return "Dice entropy is not automatically converted to base6";
2082 // dice entropy is preferred to base6 if ambiguous
2084 e
= Entropy
.fromString("12345");
2085 if (e
.base
.str
!= "base 6 (dice)") {
2086 return "dice not used as default over base 6";
2092 // unused characters are ignored
2094 e
= Entropy
.fromString("fghijkl");
2095 if (e
.cleanStr
!= "f") {
2096 return "additional characters are not ignored";
2102 // the lowest base is used by default
2103 // 7 could be decimal or hexadecimal, but should be detected as decimal
2105 e
= Entropy
.fromString("7");
2106 if (e
.base
.str
!= "base 10") {
2107 return "lowest base is not used";
2113 // Leading zeros are retained
2115 e
= Entropy
.fromString("000A");
2116 if (e
.cleanStr
!= "000A") {
2117 return "Leading zeros are not retained";
2123 // Leading zeros are correctly preserved for hex in binary string
2125 e
= Entropy
.fromString("2A");
2126 if (e
.binaryStr
!= "00101010") {
2127 return "Hex leading zeros are not correct in binary";
2133 // Leading zeros are not used for base 6 as binary string
2135 e
= Entropy
.fromString("2");
2136 if (e
.binaryStr
!= "10") {
2137 return "Base 6 as binary has leading zeros";
2143 // Leading zeros are not used for base 10 as binary string
2145 e
= Entropy
.fromString("7");
2146 if (e
.binaryStr
!= "111") {
2147 return "Base 10 as binary has leading zeros";
2153 // Leading zeros are not used for card entropy as binary string
2155 e
= Entropy
.fromString("2c");
2156 if (e
.binaryStr
!= "1") {
2157 return "Card entropy as binary has leading zeros";
2163 // Keyboard mashing results in weak entropy
2164 // Despite being a long string, it's less than 30 bits of entropy
2166 e
= Entropy
.fromString("aj;se ifj; ask,dfv js;ifj");
2167 if (e
.binaryStr
.length
>= 30) {
2168 return "Keyboard mashing should produce weak entropy";
2174 // Card entropy is used if every pair could be a card
2176 e
= Entropy
.fromString("4c3c2c");
2177 if (e
.base
.str
!= "card") {
2178 return "Card entropy not used if all pairs are cards";
2184 // Card entropy uses base 52
2185 // [ cards, binary ]
2189 [ "acac", "00000000000" ],
2190 [ "acac2c", "000000000001" ],
2191 [ "acks", "00000110011" ],
2192 [ "acacks", "00000000000110011" ],
2205 [ "ks2c", "101001011101" ],
2206 [ "KS2C", "101001011101" ],
2208 for (var i
=0; i
<cards
.length
; i
++) {
2209 var card
= cards
[i
][0];
2210 var result
= cards
[i
][1];
2211 e
= Entropy
.fromString(card
);
2212 console
.log(e
.binary
+ " " + result
);
2213 if (e
.binaryStr
!== result
) {
2214 return "card entropy not parsed correctly: " + result
+ " != " + e
.binaryStr
;
2223 if (response
!= "PASS") {
2224 console
.log("Entropy unit tests");
2225 console
.log(response
);
2232 // Entropy can be entered by the user
2234 page
.open(url
, function(status
) {
2236 mnemonic: "abandon abandon ability",
2237 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2240 page
.evaluate(function() {
2241 $(".use-entropy").prop("checked", true).trigger("change");
2242 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2244 // check the mnemonic is set and address is correct
2245 waitForGenerate(function() {
2246 var actual
= page
.evaluate(function() {
2248 address: $(".address:first").text(),
2249 mnemonic: $(".phrase").val(),
2252 if (actual
.mnemonic
!= expected
.mnemonic
) {
2253 console
.log("Entropy does not generate correct mnemonic");
2254 console
.log("Expected: " + expected
.mnemonic
);
2255 console
.log("Got: " + actual
.mnemonic
);
2258 if (actual
.address
!= expected
.address
) {
2259 console
.log("Entropy does not generate correct address");
2260 console
.log("Expected: " + expected
.address
);
2261 console
.log("Got: " + actual
.address
);
2269 // A warning about entropy is shown to the user, with additional information
2271 page
.open(url
, function(status
) {
2272 // get text content from entropy sections of page
2273 var hasWarning
= page
.evaluate(function() {
2274 var entropyText
= $(".entropy-container").text();
2275 var warning
= "mnemonic may be insecure";
2276 if (entropyText
.indexOf(warning
) == -1) {
2279 var readMoreText
= $("#entropy-notes").parent().text();
2280 var goodSources
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
2281 if (readMoreText
.indexOf(goodSources
) == -1) {
2286 // check the warnings and information are shown
2288 console
.log("Page does not contain warning about using own entropy");
2295 // The types of entropy available are described to the user
2297 page
.open(url
, function(status
) {
2298 // get placeholder text for entropy field
2299 var placeholder
= page
.evaluate(function() {
2300 return $(".entropy").attr("placeholder");
2309 for (var i
=0; i
<options
.length
; i
++) {
2310 var option
= options
[i
];
2311 if (placeholder
.indexOf(option
) == -1) {
2312 console
.log("Available entropy type is not shown to user: " + option
);
2320 // The actual entropy used is shown to the user
2322 page
.open(url
, function(status
) {
2324 var badEntropySource
= page
.evaluate(function() {
2325 var entropy
= "Not A Very Good Entropy Source At All";
2326 $(".use-entropy").prop("checked", true).trigger("change");
2327 $(".entropy").val(entropy
).trigger("input");
2329 // check the actual entropy being used is shown
2330 waitForEntropyFeedback(function() {
2331 var expectedText
= "AedEceAA";
2332 var entropyText
= page
.evaluate(function() {
2333 return $(".entropy-container").text();
2335 if (entropyText
.indexOf(expectedText
) == -1) {
2336 console
.log("Actual entropy used is not shown");
2344 // Binary entropy can be entered
2346 page
.open(url
, function(status
) {
2348 page
.evaluate(function() {
2349 $(".use-entropy").prop("checked", true).trigger("change");
2350 $(".entropy").val("01").trigger("input");
2352 // check the entropy is shown to be the correct type
2353 waitForEntropyFeedback(function() {
2354 var entropyText
= page
.evaluate(function() {
2355 return $(".entropy-container").text();
2357 if (entropyText
.indexOf("binary") == -1) {
2358 console
.log("Binary entropy is not detected and presented to user");
2366 // Base 6 entropy can be entered
2368 page
.open(url
, function(status
) {
2370 page
.evaluate(function() {
2371 $(".use-entropy").prop("checked", true).trigger("change");
2372 $(".entropy").val("012345").trigger("input");
2374 // check the entropy is shown to be the correct type
2375 waitForEntropyFeedback(function() {
2376 var entropyText
= page
.evaluate(function() {
2377 return $(".entropy-container").text();
2379 if (entropyText
.indexOf("base 6") == -1) {
2380 console
.log("Base 6 entropy is not detected and presented to user");
2388 // Base 6 dice entropy can be entered
2390 page
.open(url
, function(status
) {
2392 page
.evaluate(function() {
2393 $(".use-entropy").prop("checked", true).trigger("change");
2394 $(".entropy").val("123456").trigger("input");
2396 // check the entropy is shown to be the correct type
2397 waitForEntropyFeedback(function() {
2398 var entropyText
= page
.evaluate(function() {
2399 return $(".entropy-container").text();
2401 if (entropyText
.indexOf("dice") == -1) {
2402 console
.log("Dice entropy is not detected and presented to user");
2410 // Base 10 entropy can be entered
2412 page
.open(url
, function(status
) {
2414 page
.evaluate(function() {
2415 $(".use-entropy").prop("checked", true).trigger("change");
2416 $(".entropy").val("789").trigger("input");
2418 // check the entropy is shown to be the correct type
2419 waitForEntropyFeedback(function() {
2420 var entropyText
= page
.evaluate(function() {
2421 return $(".entropy-container").text();
2423 if (entropyText
.indexOf("base 10") == -1) {
2424 console
.log("Base 10 entropy is not detected and presented to user");
2432 // Hexadecimal entropy can be entered
2434 page
.open(url
, function(status
) {
2436 page
.evaluate(function() {
2437 $(".use-entropy").prop("checked", true).trigger("change");
2438 $(".entropy").val("abcdef").trigger("input");
2440 // check the entropy is shown to be the correct type
2441 waitForEntropyFeedback(function() {
2442 var entropyText
= page
.evaluate(function() {
2443 return $(".entropy-container").text();
2445 if (entropyText
.indexOf("hexadecimal") == -1) {
2446 console
.log("Hexadecimal entropy is not detected and presented to user");
2454 // Dice entropy value is shown as the converted base 6 value
2456 page
.open(url
, function(status
) {
2458 page
.evaluate(function() {
2459 $(".use-entropy").prop("checked", true).trigger("change");
2460 $(".entropy").val("123456").trigger("input");
2462 // check the entropy is shown as base 6, not as the original dice value
2463 waitForEntropyFeedback(function() {
2464 var entropyText
= page
.evaluate(function() {
2465 return $(".entropy-container").text();
2467 if (entropyText
.indexOf("123450") == -1) {
2468 console
.log("Dice entropy is not shown to user as base 6 value");
2471 if (entropyText
.indexOf("123456") > -1) {
2472 console
.log("Dice entropy value is shown instead of true base 6 value");
2480 // The number of bits of entropy accumulated is shown
2482 page
.open(url
, function(status
) {
2485 [ "0000 0000 0000 0000 0000", "20" ],
2488 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2489 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2494 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2501 [ "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)
2507 page
.evaluate(function(e
) {
2508 $(".use-entropy").prop("checked", true).trigger("change");
2511 var nextTest
= function runNextTest(i
) {
2512 var entropy
= tests
[i
][0];
2513 var expected
= tests
[i
][1];
2515 page
.evaluate(function(e
) {
2516 $(".entropy").val(e
).trigger("input");
2518 // check the number of bits of entropy is shown
2519 waitForEntropyFeedback(function() {
2520 var entropyText
= page
.evaluate(function() {
2521 return $(".entropy-error").text();
2523 if (entropyText
.indexOf("Have " + expected
+ " bits of entropy") == -1) {
2524 console
.log("Accumulated entropy is not shown correctly for " + entropy
);
2525 console
.log(entropyText
);
2528 var isLastTest
= i
== tests
.length
- 1;
2541 // The number of bits of entropy to reach the next mnemonic strength is shown
2543 page
.open(url
, function(status
) {
2545 page
.evaluate(function() {
2546 $(".use-entropy").prop("checked", true).trigger("change");
2547 $(".entropy").val("7654321").trigger("input");
2549 // check the amount of additional entropy required is shown
2550 waitForEntropyFeedback(function() {
2551 var entropyText
= page
.evaluate(function() {
2552 return $(".entropy-container").text();
2554 if (entropyText
.indexOf("3 more base 10 chars required") == -1) {
2555 console
.log("Additional entropy requirement is not shown");
2563 // The next strength above 0-word mnemonics is considered extremely weak
2564 // The next strength above 3-word mnemonics is considered very weak
2565 // The next strength above 6-word mnemonics is considered weak
2566 // The next strength above 9-word mnemonics is considered strong
2567 // The next strength above 12-word mnemonics is considered very strong
2568 // The next strength above 15-word mnemonics is considered extremely strong
2570 page
.open(url
, function(status
) {
2575 nextStrength: "an extremely weak",
2578 entropy: "AAAAAAAA",
2580 nextStrength: "a very weak",
2583 entropy: "AAAAAAAA B",
2585 nextStrength: "a very weak",
2588 entropy: "AAAAAAAA BBBBBBBB",
2590 nextStrength: "a weak",
2593 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2595 nextStrength: "a strong",
2598 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2600 nextStrength: "a very strong",
2603 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE",
2605 nextStrength: "an extremely strong",
2609 page
.evaluate(function() {
2610 $(".use-entropy").prop("checked", true).trigger("change");
2612 var nextTest
= function runNextTest(i
) {
2614 page
.evaluate(function(e
) {
2615 $(".addresses").empty();
2616 $(".phrase").val("");
2617 $(".entropy").val(e
).trigger("input");
2619 if (test
.words
== 0) {
2620 var mnemonic
= page
.evaluate(function() {
2621 return $(".phrase").val();
2623 if (mnemonic
.length
> 0) {
2624 console
.log("Mnemonic length for " + test
.nextStrength
+ " strength is not " + test
.words
);
2625 console
.log("Mnemonic: " + mnemonic
);
2628 var isLastTest
= i
== tests
.length
- 1;
2637 waitForGenerate(function() {
2638 // check the strength of the current mnemonic
2639 var mnemonic
= page
.evaluate(function() {
2640 return $(".phrase").val();
2642 if (mnemonic
.split(" ").length
!= test
.words
) {
2643 console
.log("Mnemonic length for " + test
.nextStrength
+ " strength is not " + test
.words
);
2644 console
.log("Mnemonic: " + mnemonic
);
2647 // check the strength of the next mnemonic is shown
2648 var entropyText
= page
.evaluate(function() {
2649 return $(".entropy-container").text();
2651 if (entropyText
.indexOf("required to generate " + test
.nextStrength
+ " mnemonic") == -1) {
2652 console
.log("Strength indicator for " + test
.nextStrength
+ " mnemonic is incorrect");
2655 var isLastTest
= i
== tests
.length
- 1;
2669 // Entropy is truncated from the right
2671 page
.open(url
, function(status
) {
2672 var expected
= "abandon abandon ability";
2674 page
.evaluate(function() {
2675 $(".use-entropy").prop("checked", true).trigger("change");
2676 var entropy
= "00000000 00000000 00000000 00000000";
2677 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte, only first 8 bytes are used
2678 $(".entropy").val(entropy
).trigger("input");
2680 // check the entropy is truncated from the right
2681 waitForGenerate(function() {
2682 var actual
= page
.evaluate(function() {
2683 return $(".phrase").val();
2685 if (actual
!= expected
) {
2686 console
.log("Entropy is not truncated from the right");
2687 console
.log("Expected: " + expected
);
2688 console
.log("Got: " + actual
);
2696 // Very large entropy results in very long mnemonics
2698 page
.open(url
, function(status
) {
2700 page
.evaluate(function() {
2701 $(".use-entropy").prop("checked", true).trigger("change");
2703 // Generate a very long entropy string
2704 for (var i
=0; i
<33; i
++) {
2705 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2707 $(".entropy").val(entropy
).trigger("input");
2709 // check the mnemonic is very long
2710 waitForGenerate(function() {
2711 var wordCount
= page
.evaluate(function() {
2712 return $(".phrase").val().split(" ").length
;
2714 if (wordCount
!= 99) {
2715 console
.log("Large entropy does not generate long mnemonic");
2716 console
.log("Expected 99 words, got " + wordCount
);
2724 // Is compatible with bip32jp entropy
2725 // https://bip32jp.github.io/english/index.html
2727 // Is incompatible with:
2728 // base 6 with leading zeros
2729 // base 6 wth 12 words / 53 chars
2732 page
.open(url
, function(status
) {
2733 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";
2735 page
.evaluate(function() {
2736 $(".use-entropy").prop("checked", true).trigger("change");
2737 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2738 $(".entropy").val(entropy
).trigger("input");
2740 // check the mnemonic matches the expected value from bip32jp
2741 waitForGenerate(function() {
2742 var actual
= page
.evaluate(function() {
2743 return $(".phrase").val();
2745 if (actual
!= expected
) {
2746 console
.log("Mnemonic does not match bip32jp for base 6 entropy");
2747 console
.log("Expected: " + expected
);
2748 console
.log("Got: " + actual
);
2756 // Blank entropy does not generate mnemonic or addresses
2758 page
.open(url
, function(status
) {
2760 page
.evaluate(function() {
2761 $(".use-entropy").prop("checked", true).trigger("change");
2762 $(".entropy").val("").trigger("input");
2764 waitForFeedback(function() {
2765 // check there is no mnemonic
2766 var phrase
= page
.evaluate(function() {
2767 return $(".phrase").val();
2770 console
.log("Blank entropy does not result in blank mnemonic");
2771 console
.log("Got: " + phrase
);
2774 // check there are no addresses displayed
2775 var addresses
= page
.evaluate(function() {
2776 return $(".address").length
;
2778 if (addresses
!= 0) {
2779 console
.log("Blank entropy does not result in zero addresses");
2782 // Check the feedback says 'blank entropy'
2783 var feedback
= page
.evaluate(function() {
2784 return $(".feedback").text();
2786 if (feedback
!= "Blank entropy") {
2787 console
.log("Blank entropy does not show feedback message");
2795 // If you wish to add more tests, do so here...
2797 // Here is a blank test template
2801 page.open(url, function(status) {
2802 // Do something on the page
2803 page.evaluate(function() {
2804 $(".phrase").val("abandon abandon ability").trigger("input");
2806 waitForGenerate(function() {
2807 // Check the result of doing the thing
2808 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
2809 var actual = page.evaluate(function() {
2810 return $(".address:first").text();
2812 if (actual != expected) {
2813 console.log("A specific message about what failed");
2814 console.log("Expected: " + expected);
2815 console.log("Actual: " + actual);
2818 // Run the next test
2828 console
.log("Running tests...");
2829 tests
= shuffle(tests
);