]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
Entropy library assumes cards are discarded
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / tests.js
1 // Usage:
2 // $ phantomjs tests.js
3
4
5 var page = require('webpage').create();
6 var url = 'src/index.html';
7 var testMaxTime = 10000;
8
9 page.onResourceError = function(e) {
10 console.log("Error loading " + e.url);
11 phantom.exit();
12 }
13
14 function fail() {
15 console.log("Failed");
16 phantom.exit();
17 }
18
19 function waitForGenerate(fn, maxTime) {
20 if (!maxTime) {
21 maxTime = testMaxTime;
22 }
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;
30 });
31 var hasFinished = addressCount > 0 && addressCount == prevAddressCount;
32 prevAddressCount = addressCount;
33 if (hasFinished) {
34 fn();
35 }
36 else if (hasTimedOut) {
37 console.log("Test timed out");
38 fn();
39 }
40 else {
41 setTimeout(keepWaiting, 100);
42 }
43 }
44 wait();
45 }
46
47 function waitForFeedback(fn, maxTime) {
48 if (!maxTime) {
49 maxTime = testMaxTime;
50 }
51 var start = new Date().getTime();
52 var wait = function keepWaiting() {
53 var now = new Date().getTime();
54 var hasTimedOut = now - start > maxTime;
55 if (hasTimedOut) {
56 console.log("Test timed out");
57 fn();
58 return;
59 }
60 var feedback = page.evaluate(function() {
61 var feedback = $(".feedback");
62 if (feedback.css("display") == "none") {
63 return "";
64 }
65 return feedback.text();
66 });
67 var hasFinished = feedback.length > 0 && feedback != "Calculating...";
68 if (hasFinished) {
69 fn();
70 }
71 else {
72 setTimeout(keepWaiting, 100);
73 }
74 }
75 wait();
76 }
77
78 function waitForEntropyFeedback(fn, maxTime) {
79 if (!maxTime) {
80 maxTime = testMaxTime;
81 }
82 var origFeedback = page.evaluate(function() {
83 return $(".entropy-container").text();
84 });
85 var start = new Date().getTime();
86 var wait = function keepWaiting() {
87 var now = new Date().getTime();
88 var hasTimedOut = now - start > maxTime;
89 if (hasTimedOut) {
90 console.log("Test timed out");
91 fn();
92 return;
93 }
94 var feedback = page.evaluate(function() {
95 return $(".entropy-container").text();
96 });
97 var hasFinished = feedback != origFeedback;
98 if (hasFinished) {
99 fn();
100 }
101 else {
102 setTimeout(keepWaiting, 100);
103 }
104 }
105 wait();
106 }
107
108 function next() {
109 if (tests.length > 0) {
110 var testsStr = tests.length == 1 ? "test" : "tests";
111 console.log(tests.length + " " + testsStr + " remaining");
112 tests.shift()();
113 }
114 else {
115 console.log("Finished with 0 failures");
116 phantom.exit();
117 }
118 }
119
120 /**
121 * Randomize array element order in-place.
122 * Using Durstenfeld shuffle algorithm.
123 * See http://stackoverflow.com/a/12646864
124 */
125 function shuffle(array) {
126 for (var i = array.length - 1; i > 0; i--) {
127 var j = Math.floor(Math.random() * (i + 1));
128 var temp = array[i];
129 array[i] = array[j];
130 array[j] = temp;
131 }
132 return array;
133 }
134
135 tests = [
136
137 // Page loads with status of 'success'
138 function() {
139 page.open(url, function(status) {
140 if (status != "success") {
141 console.log("Page did not load with status 'success'");
142 fail();
143 }
144 next();
145 });
146 },
147
148 // Page has text
149 function() {
150 page.open(url, function(status) {
151 var content = page.evaluate(function() {
152 return document.body.textContent.trim();
153 });
154 if (!content) {
155 console.log("Page does not have text");
156 fail();
157 }
158 next();
159 });
160 },
161
162 // Entering mnemonic generates addresses
163 function() {
164 page.open(url, function(status) {
165 // set the phrase
166 page.evaluate(function() {
167 $(".phrase").val("abandon abandon ability").trigger("input");
168 });
169 // get the address
170 waitForGenerate(function() {
171 var addressCount = page.evaluate(function() {
172 return $(".address").length;
173 });
174 if (addressCount != 20) {
175 console.log("Mnemonic did not generate addresses");
176 console.log("Expected: " + expected);
177 console.log("Got: " + actual);
178 fail();
179 }
180 next();
181 });
182 });
183 },
184
185 // Random button generates random mnemonic
186 function() {
187 page.open(url, function(status) {
188 // check initial phrase is empty
189 var phrase = page.evaluate(function() {
190 return $(".phrase").text();
191 });
192 if (phrase != "") {
193 console.log("Initial phrase is not blank");
194 fail();
195 }
196 // press the 'generate' button
197 page.evaluate(function() {
198 $(".generate").click();
199 });
200 // get the new phrase
201 waitForGenerate(function() {
202 var phrase = page.evaluate(function() {
203 return $(".phrase").val();
204 });
205 if (phrase.length <= 0) {
206 console.log("Phrase not generated by pressing button");
207 fail();
208 }
209 next();
210 });
211 });
212 },
213
214 // Mnemonic length can be customized
215 function() {
216 page.open(url, function(status) {
217 // set the length to 6
218 var expectedLength = "6";
219 page.evaluate(function() {
220 $(".strength option[selected]").removeAttr("selected");
221 $(".strength option[value=6]").prop("selected", true);
222 });
223 // press the 'generate' button
224 page.evaluate(function() {
225 $(".generate").click();
226 });
227 // check the new phrase is six words long
228 waitForGenerate(function() {
229 var actualLength = page.evaluate(function() {
230 var words = $(".phrase").val().split(" ");
231 return words.length;
232 });
233 if (actualLength != expectedLength) {
234 console.log("Phrase not generated with correct length");
235 console.log("Expected: " + expectedLength);
236 console.log("Actual: " + actualLength);
237 fail();
238 }
239 next();
240 });
241 });
242 },
243
244 // Passphrase can be set
245 function() {
246 page.open(url, function(status) {
247 // set the phrase and passphrase
248 var expected = "15pJzUWPGzR7avffV9nY5by4PSgSKG9rba";
249 page.evaluate(function() {
250 $(".phrase").val("abandon abandon ability");
251 $(".passphrase").val("secure_passphrase").trigger("input");
252 });
253 // check the address is generated correctly
254 waitForGenerate(function() {
255 var actual = page.evaluate(function() {
256 return $(".address:first").text();
257 });
258 if (actual != expected) {
259 console.log("Passphrase results in wrong address");
260 console.log("Expected: " + expected);
261 console.log("Actual: " + actual);
262 fail();
263 }
264 next();
265 });
266 });
267 },
268
269 // Network can be set to bitcoin testnet
270 function() {
271 page.open(url, function(status) {
272 // set the phrase and coin
273 var expected = "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi";
274 page.evaluate(function() {
275 $(".phrase").val("abandon abandon ability");
276 $(".phrase").trigger("input");
277 $(".network option[selected]").removeAttr("selected");
278 $(".network option[value=1]").prop("selected", true);
279 $(".network").trigger("change");
280 });
281 // check the address is generated correctly
282 waitForGenerate(function() {
283 var actual = page.evaluate(function() {
284 return $(".address:first").text();
285 });
286 if (actual != expected) {
287 console.log("Bitcoin testnet address is incorrect");
288 console.log("Expected: " + expected);
289 console.log("Actual: " + actual);
290 fail();
291 }
292 next();
293 });
294 });
295 },
296
297 // Network can be set to litecoin
298 function() {
299 page.open(url, function(status) {
300 // set the phrase and coin
301 var expected = "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
302 page.evaluate(function() {
303 $(".phrase").val("abandon abandon ability");
304 $(".phrase").trigger("input");
305 $(".network option[selected]").removeAttr("selected");
306 $(".network option[value=2]").prop("selected", true);
307 $(".network").trigger("change");
308 });
309 // check the address is generated correctly
310 waitForGenerate(function() {
311 var actual = page.evaluate(function() {
312 return $(".address:first").text();
313 });
314 if (actual != expected) {
315 console.log("Litecoin address is incorrect");
316 console.log("Expected: " + expected);
317 console.log("Actual: " + actual);
318 fail();
319 }
320 next();
321 });
322 });
323 },
324
325 // Network can be set to dogecoin
326 function() {
327 page.open(url, function(status) {
328 // set the phrase and coin
329 var expected = "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
330 page.evaluate(function() {
331 $(".phrase").val("abandon abandon ability");
332 $(".phrase").trigger("input");
333 $(".network option[selected]").removeAttr("selected");
334 $(".network option[value=3]").prop("selected", true);
335 $(".network").trigger("change");
336 });
337 // check the address is generated correctly
338 waitForGenerate(function() {
339 var actual = page.evaluate(function() {
340 return $(".address:first").text();
341 });
342 if (actual != expected) {
343 console.log("Dogecoin address is incorrect");
344 console.log("Expected: " + expected);
345 console.log("Actual: " + actual);
346 fail();
347 }
348 next();
349 });
350 });
351 },
352
353 // Network can be set to shadowcash
354 function() {
355 page.open(url, function(status) {
356 // set the phrase and coin
357 var expected = "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
358 page.evaluate(function() {
359 $(".phrase").val("abandon abandon ability");
360 $(".phrase").trigger("input");
361 $(".network option[selected]").removeAttr("selected");
362 $(".network option[value=4]").prop("selected", true);
363 $(".network").trigger("change");
364 });
365 // check the address is generated correctly
366 waitForGenerate(function() {
367 var actual = page.evaluate(function() {
368 return $(".address:first").text();
369 });
370 if (actual != expected) {
371 console.log("Shadowcash address is incorrect");
372 console.log("Expected: " + expected);
373 console.log("Actual: " + actual);
374 fail();
375 }
376 next();
377 });
378 });
379 },
380
381 // Network can be set to shadowcash testnet
382 function() {
383 page.open(url, function(status) {
384 // set the phrase and coin
385 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
386 page.evaluate(function() {
387 $(".phrase").val("abandon abandon ability");
388 $(".phrase").trigger("input");
389 $(".network option[selected]").removeAttr("selected");
390 $(".network option[value=5]").prop("selected", true);
391 $(".network").trigger("change");
392 });
393 // check the address is generated correctly
394 waitForGenerate(function() {
395 var actual = page.evaluate(function() {
396 return $(".address:first").text();
397 });
398 if (actual != expected) {
399 console.log("Shadowcash testnet address is incorrect");
400 console.log("Expected: " + expected);
401 console.log("Actual: " + actual);
402 fail();
403 }
404 next();
405 });
406 });
407 },
408
409 // Network can be set to viacoin
410 function() {
411 page.open(url, function(status) {
412 // set the phrase and coin
413 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
414 page.evaluate(function() {
415 $(".phrase").val("abandon abandon ability");
416 $(".phrase").trigger("input");
417 $(".network option[selected]").removeAttr("selected");
418 $(".network option[value=6]").prop("selected", true);
419 $(".network").trigger("change");
420 });
421 // check the address is generated correctly
422 waitForGenerate(function() {
423 var actual = page.evaluate(function() {
424 return $(".address:first").text();
425 });
426 if (actual != expected) {
427 console.log("Viacoin address is incorrect");
428 console.log("Expected: " + expected);
429 console.log("Actual: " + actual);
430 fail();
431 }
432 next();
433 });
434 });
435 },
436
437 // Network can be set to viacoin testnet
438 function() {
439 page.open(url, function(status) {
440 // set the phrase and coin
441 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
442 page.evaluate(function() {
443 $(".phrase").val("abandon abandon ability");
444 $(".phrase").trigger("input");
445 $(".network option[selected]").removeAttr("selected");
446 $(".network option[value=7]").prop("selected", true);
447 $(".network").trigger("change");
448 });
449 // check the address is generated correctly
450 waitForGenerate(function() {
451 var actual = page.evaluate(function() {
452 return $(".address:first").text();
453 });
454 if (actual != expected) {
455 console.log("Viacoin testnet address is incorrect");
456 console.log("Expected: " + expected);
457 console.log("Actual: " + actual);
458 fail();
459 }
460 next();
461 });
462 });
463 },
464
465 // Network can be set to jumbucks
466 function() {
467 page.open(url, function(status) {
468 // set the phrase and coin
469 var expected = "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
470 page.evaluate(function() {
471 $(".phrase").val("abandon abandon ability");
472 $(".phrase").trigger("input");
473 $(".network option[selected]").removeAttr("selected");
474 $(".network option[value=8]").prop("selected", true);
475 $(".network").trigger("change");
476 });
477 // check the address is generated correctly
478 waitForGenerate(function() {
479 var actual = page.evaluate(function() {
480 return $(".address:first").text();
481 });
482 if (actual != expected) {
483 console.log("Jumbucks address is incorrect");
484 console.log("Expected: " + expected);
485 console.log("Actual: " + actual);
486 fail();
487 }
488 next();
489 });
490 });
491 },
492
493 // Network can be set to clam
494 function() {
495 page.open(url, function(status) {
496 // set the phrase and coin
497 var expected = "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
498 page.evaluate(function() {
499 $(".phrase").val("abandon abandon ability");
500 $(".phrase").trigger("input");
501 $(".network option[selected]").removeAttr("selected");
502 $(".network option[value=9]").prop("selected", true);
503 $(".network").trigger("change");
504 });
505 // check the address is generated correctly
506 waitForGenerate(function() {
507 var actual = page.evaluate(function() {
508 return $(".address:first").text();
509 });
510 if (actual != expected) {
511 console.log("CLAM address is incorrect");
512 console.log("Expected: " + expected);
513 console.log("Actual: " + actual);
514 fail();
515 }
516 next();
517 });
518 });
519 },
520
521 // Network can be set to dash
522 function() {
523 page.open(url, function(status) {
524 // set the phrase and coin
525 var expected = "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
526 page.evaluate(function() {
527 $(".phrase").val("abandon abandon ability");
528 $(".phrase").trigger("input");
529 $(".network option[selected]").removeAttr("selected");
530 $(".network option[value=10]").prop("selected", true);
531 $(".network").trigger("change");
532 });
533 // check the address is generated correctly
534 waitForGenerate(function() {
535 var actual = page.evaluate(function() {
536 return $(".address:first").text();
537 });
538 if (actual != expected) {
539 console.log("DASH address is incorrect");
540 console.log("Expected: " + expected);
541 console.log("Actual: " + actual);
542 fail();
543 }
544 next();
545 });
546 });
547 },
548
549 // Network can be set to namecoin
550 function() {
551 page.open(url, function(status) {
552 // set the phrase and coin
553 var expected = "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
554 page.evaluate(function() {
555 $(".phrase").val("abandon abandon ability");
556 $(".phrase").trigger("input");
557 $(".network option[selected]").removeAttr("selected");
558 $(".network option[value=11]").prop("selected", true);
559 $(".network").trigger("change");
560 });
561 // check the address is generated correctly
562 waitForGenerate(function() {
563 var actual = page.evaluate(function() {
564 return $(".address:first").text();
565 });
566 if (actual != expected) {
567 console.log("Namecoin address is incorrect");
568 console.log("Expected: " + expected);
569 console.log("Actual: " + actual);
570 fail();
571 }
572 next();
573 });
574 });
575 },
576
577 // Network can be set to peercoin
578 function() {
579 page.open(url, function(status) {
580 // set the phrase and coin
581 var expected = "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
582 page.evaluate(function() {
583 $(".phrase").val("abandon abandon ability");
584 $(".phrase").trigger("input");
585 $(".network option[selected]").removeAttr("selected");
586 $(".network option[value=12]").prop("selected", true);
587 $(".network").trigger("change");
588 });
589 // check the address is generated correctly
590 waitForGenerate(function() {
591 var actual = page.evaluate(function() {
592 return $(".address:first").text();
593 });
594 if (actual != expected) {
595 console.log("Peercoin address is incorrect");
596 console.log("Expected: " + expected);
597 console.log("Actual: " + actual);
598 fail();
599 }
600 next();
601 });
602 });
603 },
604
605 // BIP39 seed is set from phrase
606 function() {
607 page.open(url, function(status) {
608 // set the phrase
609 var expected = "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
610 page.evaluate(function() {
611 $(".phrase").val("abandon abandon ability");
612 $(".phrase").trigger("input");
613 });
614 // check the address is generated correctly
615 waitForGenerate(function() {
616 var actual = page.evaluate(function() {
617 return $(".seed").val();
618 });
619 if (actual != expected) {
620 console.log("BIP39 seed is incorrectly generated from mnemonic");
621 console.log("Expected: " + expected);
622 console.log("Actual: " + actual);
623 fail();
624 }
625 next();
626 });
627 });
628 },
629
630 // BIP32 root key is set from phrase
631 function() {
632 page.open(url, function(status) {
633 // set the phrase
634 var expected = "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
635 page.evaluate(function() {
636 $(".phrase").val("abandon abandon ability");
637 $(".phrase").trigger("input");
638 });
639 // check the address is generated correctly
640 waitForGenerate(function() {
641 var actual = page.evaluate(function() {
642 return $(".root-key").val();
643 });
644 if (actual != expected) {
645 console.log("Root key is incorrectly generated from mnemonic");
646 console.log("Expected: " + expected);
647 console.log("Actual: " + actual);
648 fail();
649 }
650 next();
651 });
652 });
653 },
654
655 // Tabs show correct addresses when changed
656 function() {
657 page.open(url, function(status) {
658 // set the phrase
659 var expected = "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
660 page.evaluate(function() {
661 $(".phrase").val("abandon abandon ability");
662 $(".phrase").trigger("input");
663 });
664 // change tabs
665 waitForGenerate(function() {
666 page.evaluate(function() {
667 $("#bip32-tab a").click();
668 });
669 // check the address is generated correctly
670 waitForGenerate(function() {
671 var actual = page.evaluate(function() {
672 return $(".address:first").text();
673 });
674 if (actual != expected) {
675 console.log("Clicking tab generates incorrect address");
676 console.log("Expected: " + expected);
677 console.log("Actual: " + actual);
678 fail();
679 }
680 next();
681 });
682 });
683 });
684 },
685
686 // BIP44 derivation path is shown
687 function() {
688 page.open(url, function(status) {
689 // set the phrase
690 var expected = "m/44'/0'/0'/0";
691 page.evaluate(function() {
692 $(".phrase").val("abandon abandon ability");
693 $(".phrase").trigger("input");
694 });
695 // check the derivation path of the first address
696 waitForGenerate(function() {
697 var actual = page.evaluate(function() {
698 return $("#bip44 .path").val();
699 });
700 if (actual != expected) {
701 console.log("BIP44 derivation path is incorrect");
702 console.log("Expected: " + expected);
703 console.log("Actual: " + actual);
704 fail();
705 }
706 next();
707 });
708 });
709 },
710
711 // BIP44 extended private key is shown
712 function() {
713 page.open(url, function(status) {
714 // set the phrase
715 var expected = "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
716 page.evaluate(function() {
717 $(".phrase").val("abandon abandon ability");
718 $(".phrase").trigger("input");
719 });
720 // check the BIP44 extended private key
721 waitForGenerate(function() {
722 var actual = page.evaluate(function() {
723 return $(".extended-priv-key").val();
724 });
725 if (actual != expected) {
726 console.log("BIP44 extended private key is incorrect");
727 console.log("Expected: " + expected);
728 console.log("Actual: " + actual);
729 fail();
730 }
731 next();
732 });
733 });
734 },
735
736 // BIP44 extended public key is shown
737 function() {
738 page.open(url, function(status) {
739 // set the phrase
740 var expected = "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
741 page.evaluate(function() {
742 $(".phrase").val("abandon abandon ability");
743 $(".phrase").trigger("input");
744 });
745 // check the BIP44 extended public key
746 waitForGenerate(function() {
747 var actual = page.evaluate(function() {
748 return $(".extended-pub-key").val();
749 });
750 if (actual != expected) {
751 console.log("BIP44 extended public key is incorrect");
752 console.log("Expected: " + expected);
753 console.log("Actual: " + actual);
754 fail();
755 }
756 next();
757 });
758 });
759 },
760
761 // BIP44 purpose field changes address list
762 function() {
763 page.open(url, function(status) {
764 // set the phrase
765 var expected = "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
766 page.evaluate(function() {
767 $(".phrase").val("abandon abandon ability");
768 $(".phrase").trigger("input");
769 });
770 waitForGenerate(function() {
771 // change the bip44 purpose field to 45
772 page.evaluate(function() {
773 $("#bip44 .purpose").val("45");
774 $("#bip44 .purpose").trigger("input");
775 });
776 waitForGenerate(function() {
777 // check the address for the new derivation path
778 var actual = page.evaluate(function() {
779 return $(".address:first").text();
780 });
781 if (actual != expected) {
782 console.log("BIP44 purpose field generates incorrect address");
783 console.log("Expected: " + expected);
784 console.log("Actual: " + actual);
785 fail();
786 }
787 next();
788 });
789 });
790 });
791 },
792
793 // BIP44 coin field changes address list
794 function() {
795 page.open(url, function(status) {
796 // set the phrase
797 var expected = "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
798 page.evaluate(function() {
799 $(".phrase").val("abandon abandon ability");
800 $(".phrase").trigger("input");
801 });
802 waitForGenerate(function() {
803 // change the bip44 purpose field to 45
804 page.evaluate(function() {
805 $("#bip44 .coin").val("1");
806 $("#bip44 .coin").trigger("input");
807 });
808 waitForGenerate(function() {
809 // check the address for the new derivation path
810 var actual = page.evaluate(function() {
811 return $(".address:first").text();
812 });
813 if (actual != expected) {
814 console.log("BIP44 coin field generates incorrect address");
815 console.log("Expected: " + expected);
816 console.log("Actual: " + actual);
817 fail();
818 }
819 next();
820 });
821 });
822 });
823 },
824
825 // BIP44 account field changes address list
826 function() {
827 page.open(url, function(status) {
828 // set the phrase
829 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
830 page.evaluate(function() {
831 $(".phrase").val("abandon abandon ability");
832 $(".phrase").trigger("input");
833 });
834 waitForGenerate(function() {
835 // change the bip44 purpose field to 45
836 page.evaluate(function() {
837 $("#bip44 .account").val("1");
838 $("#bip44 .account").trigger("input");
839 });
840 waitForGenerate(function() {
841 // check the address for the new derivation path
842 var actual = page.evaluate(function() {
843 return $(".address:first").text();
844 });
845 if (actual != expected) {
846 console.log("BIP44 account field generates incorrect address");
847 console.log("Expected: " + expected);
848 console.log("Actual: " + actual);
849 fail();
850 }
851 next();
852 });
853 });
854 });
855 },
856
857 // BIP44 change field changes address list
858 function() {
859 page.open(url, function(status) {
860 // set the phrase
861 var expected = "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
862 page.evaluate(function() {
863 $(".phrase").val("abandon abandon ability");
864 $(".phrase").trigger("input");
865 });
866 waitForGenerate(function() {
867 // change the bip44 purpose field to 45
868 page.evaluate(function() {
869 $("#bip44 .change").val("1");
870 $("#bip44 .change").trigger("input");
871 });
872 waitForGenerate(function() {
873 // check the address for the new derivation path
874 var actual = page.evaluate(function() {
875 return $(".address:first").text();
876 });
877 if (actual != expected) {
878 console.log("BIP44 change field generates incorrect address");
879 console.log("Expected: " + expected);
880 console.log("Actual: " + actual);
881 fail();
882 }
883 next();
884 });
885 });
886 });
887 },
888
889 // BIP32 derivation path can be set
890 function() {
891 page.open(url, function(status) {
892 // set the phrase
893 var expected = "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
894 page.evaluate(function() {
895 $(".phrase").val("abandon abandon ability");
896 $(".phrase").trigger("input");
897 });
898 // change tabs
899 waitForGenerate(function() {
900 page.evaluate(function() {
901 $("#bip32-tab a").click();
902 });
903 // set the derivation path to m/1
904 waitForGenerate(function() {
905 page.evaluate(function() {
906 $("#bip32 .path").val("m/1");
907 $("#bip32 .path").trigger("input");
908 });
909 // check the address is generated correctly
910 waitForGenerate(function() {
911 var actual = page.evaluate(function() {
912 return $(".address:first").text();
913 });
914 if (actual != expected) {
915 console.log("Custom BIP32 path generates incorrect address");
916 console.log("Expected: " + expected);
917 console.log("Actual: " + actual);
918 fail();
919 }
920 next();
921 });
922 });
923 });
924 });
925 },
926
927 // BIP32 can use hardened derivation paths
928 function() {
929 page.open(url, function(status) {
930 // set the phrase
931 var expected = "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
932 page.evaluate(function() {
933 $(".phrase").val("abandon abandon ability");
934 $(".phrase").trigger("input");
935 });
936 // change tabs
937 waitForGenerate(function() {
938 page.evaluate(function() {
939 $("#bip32-tab a").click();
940 });
941 // set the derivation path to m/0'
942 waitForGenerate(function() {
943 page.evaluate(function() {
944 $("#bip32 .path").val("m/0'");
945 $("#bip32 .path").trigger("input");
946 });
947 // check the address is generated correctly
948 waitForGenerate(function() {
949 var actual = page.evaluate(function() {
950 return $(".address:first").text();
951 });
952 if (actual != expected) {
953 console.log("Hardened BIP32 path generates incorrect address");
954 console.log("Expected: " + expected);
955 console.log("Actual: " + actual);
956 fail();
957 }
958 next();
959 });
960 });
961 });
962 });
963 },
964
965 // BIP32 extended private key is shown
966 function() {
967 page.open(url, function(status) {
968 // set the phrase
969 var expected = "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
970 page.evaluate(function() {
971 $(".phrase").val("abandon abandon ability");
972 $(".phrase").trigger("input");
973 });
974 // change tabs
975 waitForGenerate(function() {
976 page.evaluate(function() {
977 $("#bip32-tab a").click();
978 });
979 // check the extended private key is generated correctly
980 waitForGenerate(function() {
981 var actual = page.evaluate(function() {
982 return $(".extended-priv-key").val();
983 });
984 if (actual != expected) {
985 console.log("BIP32 extended private key is incorrect");
986 console.log("Expected: " + expected);
987 console.log("Actual: " + actual);
988 fail();
989 }
990 next();
991 });
992 });
993 });
994 },
995
996 // BIP32 extended public key is shown
997 function() {
998 page.open(url, function(status) {
999 // set the phrase
1000 var expected = "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1001 page.evaluate(function() {
1002 $(".phrase").val("abandon abandon ability");
1003 $(".phrase").trigger("input");
1004 });
1005 // change tabs
1006 waitForGenerate(function() {
1007 page.evaluate(function() {
1008 $("#bip32-tab a").click();
1009 });
1010 // check the extended public key is generated correctly
1011 waitForGenerate(function() {
1012 var actual = page.evaluate(function() {
1013 return $(".extended-pub-key").val();
1014 });
1015 if (actual != expected) {
1016 console.log("BIP32 extended public key is incorrect");
1017 console.log("Expected: " + expected);
1018 console.log("Actual: " + actual);
1019 fail();
1020 }
1021 next();
1022 });
1023 });
1024 });
1025 },
1026
1027 // Derivation path is shown in table
1028 function() {
1029 page.open(url, function(status) {
1030 // set the phrase
1031 var expected = "m/44'/0'/0'/0/0";
1032 page.evaluate(function() {
1033 $(".phrase").val("abandon abandon ability");
1034 $(".phrase").trigger("input");
1035 });
1036 // check for derivation path in table
1037 waitForGenerate(function() {
1038 var actual = page.evaluate(function() {
1039 return $(".index:first").text();
1040 });
1041 if (actual != expected) {
1042 console.log("Derivation path shown incorrectly in table");
1043 console.log("Expected: " + expected);
1044 console.log("Actual: " + actual);
1045 fail();
1046 }
1047 next();
1048 });
1049 });
1050 },
1051
1052 // Derivation path for address can be hardened
1053 function() {
1054 page.open(url, function(status) {
1055 // set the phrase
1056 var expected = "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1057 page.evaluate(function() {
1058 $(".phrase").val("abandon abandon ability");
1059 $(".phrase").trigger("input");
1060 });
1061 // change tabs
1062 waitForGenerate(function() {
1063 page.evaluate(function() {
1064 $("#bip32-tab a").click();
1065 });
1066 waitForGenerate(function() {
1067 // select the hardened addresses option
1068 page.evaluate(function() {
1069 $(".hardened-addresses").prop("checked", true);
1070 $(".hardened-addresses").trigger("change");
1071 });
1072 waitForGenerate(function() {
1073 // check the generated address is hardened
1074 var actual = page.evaluate(function() {
1075 return $(".address:first").text();
1076 });
1077 if (actual != expected) {
1078 console.log("Hardened address is incorrect");
1079 console.log("Expected: " + expected);
1080 console.log("Actual: " + actual);
1081 fail();
1082 }
1083 next();
1084 });
1085 });
1086 });
1087 });
1088 },
1089
1090 // Derivation path visibility can be toggled
1091 function() {
1092 page.open(url, function(status) {
1093 // set the phrase
1094 page.evaluate(function() {
1095 $(".phrase").val("abandon abandon ability");
1096 $(".phrase").trigger("input");
1097 });
1098 waitForGenerate(function() {
1099 // toggle path visibility
1100 page.evaluate(function() {
1101 $(".index-toggle").click();
1102 });
1103 // check the path is not visible
1104 var isInvisible = page.evaluate(function() {
1105 return $(".index:first span").hasClass("invisible");
1106 });
1107 if (!isInvisible) {
1108 console.log("Toggled derivation path is visible");
1109 fail();
1110 }
1111 next();
1112 });
1113 });
1114 },
1115
1116 // Address is shown
1117 function() {
1118 page.open(url, function(status) {
1119 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1120 // set the phrase
1121 page.evaluate(function() {
1122 $(".phrase").val("abandon abandon ability").trigger("input");
1123 });
1124 // get the address
1125 waitForGenerate(function() {
1126 var actual = page.evaluate(function() {
1127 return $(".address:first").text();
1128 });
1129 if (actual != expected) {
1130 console.log("Address is not shown");
1131 console.log("Expected: " + expected);
1132 console.log("Got: " + actual);
1133 fail();
1134 }
1135 next();
1136 });
1137 });
1138 },
1139
1140 // Addresses are shown in order of derivation path
1141 function() {
1142 page.open(url, function(status) {
1143 // set the phrase
1144 page.evaluate(function() {
1145 $(".phrase").val("abandon abandon ability").trigger("input");
1146 });
1147 // get the derivation paths
1148 waitForGenerate(function() {
1149 var paths = page.evaluate(function() {
1150 return $(".index").map(function(i, e) {
1151 return $(e).text();
1152 });
1153 });
1154 if (paths.length != 20) {
1155 console.log("Total paths is less than expected: " + paths.length);
1156 fail();
1157 }
1158 for (var i=0; i<paths.length; i++) {
1159 var expected = "m/44'/0'/0'/0/" + i;
1160 var actual = paths[i];
1161 if (actual != expected) {
1162 console.log("Path " + i + " is incorrect");
1163 console.log("Expected: " + expected);
1164 console.log("Actual: " + actual);
1165 fail();
1166 }
1167 }
1168 next();
1169 });
1170 });
1171 },
1172
1173 // Address visibility can be toggled
1174 function() {
1175 page.open(url, function(status) {
1176 // set the phrase
1177 page.evaluate(function() {
1178 $(".phrase").val("abandon abandon ability");
1179 $(".phrase").trigger("input");
1180 });
1181 waitForGenerate(function() {
1182 // toggle address visibility
1183 page.evaluate(function() {
1184 $(".address-toggle").click();
1185 });
1186 // check the address is not visible
1187 var isInvisible = page.evaluate(function() {
1188 return $(".address:first span").hasClass("invisible");
1189 });
1190 if (!isInvisible) {
1191 console.log("Toggled address is visible");
1192 fail();
1193 }
1194 next();
1195 });
1196 });
1197 },
1198
1199 // Public key is shown
1200 function() {
1201 page.open(url, function(status) {
1202 var expected = "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1203 // set the phrase
1204 page.evaluate(function() {
1205 $(".phrase").val("abandon abandon ability").trigger("input");
1206 });
1207 // get the address
1208 waitForGenerate(function() {
1209 var actual = page.evaluate(function() {
1210 return $(".pubkey:first").text();
1211 });
1212 if (actual != expected) {
1213 console.log("Public key is not shown");
1214 console.log("Expected: " + expected);
1215 console.log("Got: " + actual);
1216 fail();
1217 }
1218 next();
1219 });
1220 });
1221 },
1222
1223 // Public key visibility can be toggled
1224 function() {
1225 page.open(url, function(status) {
1226 // set the phrase
1227 page.evaluate(function() {
1228 $(".phrase").val("abandon abandon ability");
1229 $(".phrase").trigger("input");
1230 });
1231 waitForGenerate(function() {
1232 // toggle public key visibility
1233 page.evaluate(function() {
1234 $(".public-key-toggle").click();
1235 });
1236 // check the public key is not visible
1237 var isInvisible = page.evaluate(function() {
1238 return $(".pubkey:first span").hasClass("invisible");
1239 });
1240 if (!isInvisible) {
1241 console.log("Toggled public key is visible");
1242 fail();
1243 }
1244 next();
1245 });
1246 });
1247 },
1248
1249 // Private key is shown
1250 function() {
1251 page.open(url, function(status) {
1252 var expected = "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1253 // set the phrase
1254 page.evaluate(function() {
1255 $(".phrase").val("abandon abandon ability").trigger("input");
1256 });
1257 // get the address
1258 waitForGenerate(function() {
1259 var actual = page.evaluate(function() {
1260 return $(".privkey:first").text();
1261 });
1262 if (actual != expected) {
1263 console.log("Private key is not shown");
1264 console.log("Expected: " + expected);
1265 console.log("Got: " + actual);
1266 fail();
1267 }
1268 next();
1269 });
1270 });
1271 },
1272
1273 // Private key visibility can be toggled
1274 function() {
1275 page.open(url, function(status) {
1276 // set the phrase
1277 page.evaluate(function() {
1278 $(".phrase").val("abandon abandon ability");
1279 $(".phrase").trigger("input");
1280 });
1281 waitForGenerate(function() {
1282 // toggle private key visibility
1283 page.evaluate(function() {
1284 $(".private-key-toggle").click();
1285 });
1286 // check the private key is not visible
1287 var isInvisible = page.evaluate(function() {
1288 return $(".privkey:first span").hasClass("invisible");
1289 });
1290 if (!isInvisible) {
1291 console.log("Toggled private key is visible");
1292 fail();
1293 }
1294 next();
1295 });
1296 });
1297 },
1298
1299 // More addresses can be generated
1300 function() {
1301 page.open(url, function(status) {
1302 // set the phrase
1303 page.evaluate(function() {
1304 $(".phrase").val("abandon abandon ability");
1305 $(".phrase").trigger("input");
1306 });
1307 waitForGenerate(function() {
1308 // generate more addresses
1309 page.evaluate(function() {
1310 $(".more").click();
1311 });
1312 waitForGenerate(function() {
1313 // check there are more addresses
1314 var addressCount = page.evaluate(function() {
1315 return $(".address").length;
1316 });
1317 if (addressCount != 40) {
1318 console.log("More addresses cannot be generated");
1319 fail();
1320 }
1321 next();
1322 });
1323 });
1324 });
1325 },
1326
1327 // A custom number of additional addresses can be generated
1328 function() {
1329 page.open(url, function(status) {
1330 // set the phrase
1331 page.evaluate(function() {
1332 $(".phrase").val("abandon abandon ability");
1333 $(".phrase").trigger("input");
1334 });
1335 waitForGenerate(function() {
1336 // get the current number of addresses
1337 var oldAddressCount = page.evaluate(function() {
1338 return $(".address").length;
1339 });
1340 // set a custom number of additional addresses
1341 page.evaluate(function() {
1342 $(".rows-to-add").val(1);
1343 });
1344 // generate more addresses
1345 page.evaluate(function() {
1346 $(".more").click();
1347 });
1348 waitForGenerate(function() {
1349 // check there are the correct number of addresses
1350 var newAddressCount = page.evaluate(function() {
1351 return $(".address").length;
1352 });
1353 if (newAddressCount - oldAddressCount != 1) {
1354 console.log("Number of additional addresses cannot be customized");
1355 console.log(newAddressCount)
1356 console.log(oldAddressCount)
1357 fail();
1358 }
1359 next();
1360 });
1361 });
1362 });
1363 },
1364
1365 // Additional addresses are shown in order of derivation path
1366 function() {
1367 page.open(url, function(status) {
1368 // set the phrase
1369 page.evaluate(function() {
1370 $(".phrase").val("abandon abandon ability").trigger("input");
1371 });
1372 waitForGenerate(function() {
1373 // generate more addresses
1374 page.evaluate(function() {
1375 $(".more").click();
1376 });
1377 // get the derivation paths
1378 waitForGenerate(function() {
1379 var paths = page.evaluate(function() {
1380 return $(".index").map(function(i, e) {
1381 return $(e).text();
1382 });
1383 });
1384 if (paths.length != 40) {
1385 console.log("Total additional paths is less than expected: " + paths.length);
1386 fail();
1387 }
1388 for (var i=0; i<paths.length; i++) {
1389 var expected = "m/44'/0'/0'/0/" + i;
1390 var actual = paths[i];
1391 if (actual != expected) {
1392 console.log("Path " + i + " is not in correct order");
1393 console.log("Expected: " + expected);
1394 console.log("Actual: " + actual);
1395 fail();
1396 }
1397 }
1398 next();
1399 });
1400 });
1401 });
1402 },
1403
1404 // BIP32 root key can be set by the user
1405 function() {
1406 page.open(url, function(status) {
1407 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1408 // set the root key
1409 page.evaluate(function() {
1410 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1411 });
1412 waitForGenerate(function() {
1413 var actual = page.evaluate(function() {
1414 return $(".address:first").text();
1415 });
1416 if (actual != expected) {
1417 console.log("Setting BIP32 root key results in wrong address");
1418 console.log("Expected: " + expected);
1419 console.log("Actual: " + actual);
1420 fail();
1421 }
1422 next();
1423 });
1424 });
1425 },
1426
1427 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1428 function() {
1429 page.open(url, function(status) {
1430 var expected = "";
1431 // set a mnemonic
1432 page.evaluate(function() {
1433 $(".phrase").val("A non-blank but invalid value");
1434 });
1435 // Accept any confirm dialogs
1436 page.onConfirm = function() {
1437 return true;
1438 };
1439 // set the root key
1440 page.evaluate(function() {
1441 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1442 });
1443 waitForGenerate(function() {
1444 var actual = page.evaluate(function() {
1445 return $(".phrase").val();
1446 });
1447 if (actual != expected) {
1448 console.log("Phrase not cleared when setting BIP32 root key");
1449 console.log("Expected: " + expected);
1450 console.log("Actual: " + actual);
1451 fail();
1452 }
1453 next();
1454 });
1455 });
1456 },
1457
1458 // Clearing of phrase, passphrase and seed can be cancelled by user
1459 function() {
1460 page.open(url, function(status) {
1461 var expected = "abandon abandon ability";
1462 // set a mnemonic
1463 page.evaluate(function() {
1464 $(".phrase").val("abandon abandon ability");
1465 });
1466 // Cancel any confirm dialogs
1467 page.onConfirm = function() {
1468 return false;
1469 };
1470 // set the root key
1471 page.evaluate(function() {
1472 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1473 });
1474 var actual = page.evaluate(function() {
1475 return $(".phrase").val();
1476 });
1477 if (actual != expected) {
1478 console.log("Phrase not retained when cancelling changes to BIP32 root key");
1479 console.log("Expected: " + expected);
1480 console.log("Actual: " + actual);
1481 fail();
1482 }
1483 next();
1484 });
1485 },
1486
1487 // Custom BIP32 root key is used when changing the derivation path
1488 function() {
1489 page.open(url, function(status) {
1490 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1491 // set the root key
1492 page.evaluate(function() {
1493 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1494 });
1495 waitForGenerate(function() {
1496 // change the derivation path
1497 page.evaluate(function() {
1498 $("#account").val("1").trigger("input");
1499 });
1500 // check the bip32 root key is used for derivation, not the blank phrase
1501 waitForGenerate(function() {
1502 var actual = page.evaluate(function() {
1503 return $(".address:first").text();
1504 });
1505 if (actual != expected) {
1506 console.log("Changing the derivation path does not use BIP32 root key");
1507 console.log("Expected: " + expected);
1508 console.log("Actual: " + actual);
1509 fail();
1510 }
1511 next();
1512 });
1513 });
1514 });
1515 },
1516
1517 // Incorrect mnemonic shows error
1518 function() {
1519 page.open(url, function(status) {
1520 // set the root key
1521 page.evaluate(function() {
1522 $(".phrase").val("abandon abandon abandon").trigger("input");
1523 });
1524 waitForFeedback(function() {
1525 // check there is an error shown
1526 var feedback = page.evaluate(function() {
1527 return $(".feedback").text();
1528 });
1529 if (feedback.length <= 0) {
1530 console.log("Invalid mnemonic does not show error");
1531 fail();
1532 }
1533 next();
1534 });
1535 });
1536 },
1537
1538 // Incorrect word shows suggested replacement
1539 function() {
1540 page.open(url, function(status) {
1541 // set the root key
1542 page.evaluate(function() {
1543 $(".phrase").val("abandon abandon abiliti").trigger("input");
1544 });
1545 // check there is a suggestion shown
1546 waitForFeedback(function() {
1547 var feedback = page.evaluate(function() {
1548 return $(".feedback").text();
1549 });
1550 if (feedback.indexOf("did you mean ability?") < 0) {
1551 console.log("Incorrect word does not show suggested replacement");
1552 console.log("Error: " + error);
1553 fail();
1554 }
1555 next();
1556 });
1557 });
1558 },
1559
1560 // Incorrect BIP32 root key shows error
1561 function() {
1562 page.open(url, function(status) {
1563 // set the root key
1564 page.evaluate(function() {
1565 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1566 });
1567 // check there is an error shown
1568 waitForFeedback(function() {
1569 var feedback = page.evaluate(function() {
1570 return $(".feedback").text();
1571 });
1572 if (feedback != "Invalid root key") {
1573 console.log("Invalid root key does not show error");
1574 console.log("Error: " + error);
1575 fail();
1576 }
1577 next();
1578 });
1579 });
1580 },
1581
1582 // Derivation path not starting with m shows error
1583 function() {
1584 page.open(url, function(status) {
1585 // set the mnemonic phrase
1586 page.evaluate(function() {
1587 $(".phrase").val("abandon abandon ability").trigger("input");
1588 });
1589 waitForGenerate(function() {
1590 // select the bip32 tab so custom derivation path can be set
1591 page.evaluate(function() {
1592 $("#bip32-tab a").click();
1593 });
1594 waitForGenerate(function() {
1595 // set the incorrect derivation path
1596 page.evaluate(function() {
1597 $("#bip32 .path").val("n/0").trigger("input");
1598 });
1599 waitForFeedback(function() {
1600 var feedback = page.evaluate(function() {
1601 return $(".feedback").text();
1602 });
1603 if (feedback != "First character must be 'm'") {
1604 console.log("Derivation path not starting with m should show error");
1605 console.log("Error: " + error);
1606 fail();
1607 }
1608 next();
1609 });
1610 });
1611 });
1612 });
1613 },
1614
1615 // Derivation path containing invalid characters shows useful error
1616 function() {
1617 page.open(url, function(status) {
1618 // set the mnemonic phrase
1619 page.evaluate(function() {
1620 $(".phrase").val("abandon abandon ability").trigger("input");
1621 });
1622 waitForGenerate(function() {
1623 // select the bip32 tab so custom derivation path can be set
1624 page.evaluate(function() {
1625 $("#bip32-tab a").click();
1626 });
1627 waitForGenerate(function() {
1628 // set the incorrect derivation path
1629 page.evaluate(function() {
1630 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1631 });
1632 waitForFeedback(function() {
1633 var feedback = page.evaluate(function() {
1634 return $(".feedback").text();
1635 });
1636 if (feedback != "Invalid characters 0wrong1 found at depth 2") {
1637 console.log("Derivation path with invalid characters should show error");
1638 console.log("Error: " + error);
1639 fail();
1640 }
1641 next();
1642 });
1643 });
1644 });
1645 });
1646 },
1647
1648 // Github Issue 11: Default word length is 15
1649 // https://github.com/iancoleman/bip39/issues/11
1650 function() {
1651 page.open(url, function(status) {
1652 // get the word length
1653 var defaultLength = page.evaluate(function() {
1654 return $(".strength").val();
1655 });
1656 if (defaultLength != 15) {
1657 console.log("Default word length is not 15");
1658 fail();
1659 }
1660 next();
1661 });
1662 },
1663
1664
1665 // Github Issue 12: Generate more rows with private keys hidden
1666 // https://github.com/iancoleman/bip39/issues/12
1667 function() {
1668 page.open(url, function(status) {
1669 // set the phrase
1670 page.evaluate(function() {
1671 $(".phrase").val("abandon abandon ability");
1672 $(".phrase").trigger("input");
1673 });
1674 waitForGenerate(function() {
1675 // toggle private keys hidden, then generate more addresses
1676 page.evaluate(function() {
1677 $(".private-key-toggle").click();
1678 $(".more").click();
1679 });
1680 waitForGenerate(function() {
1681 // check more have been generated
1682 var expected = 40;
1683 var numPrivKeys = page.evaluate(function() {
1684 return $(".privkey").length;
1685 });
1686 if (numPrivKeys != expected) {
1687 console.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1688 console.log("Expected: " + expected);
1689 console.log("Actual: " + numPrivKeys);
1690 fail();
1691 }
1692 // check no private keys are shown
1693 var numHiddenPrivKeys = page.evaluate(function() {
1694 return $(".privkey span[class=invisible]").length;
1695 });
1696 if (numHiddenPrivKeys != expected) {
1697 console.log("Generating more does not retain hidden state of privkeys");
1698 console.log("Expected: " + expected);
1699 console.log("Actual: " + numHiddenPrivKeys);
1700 fail();
1701 }
1702 next();
1703 });
1704 });
1705 });
1706 },
1707
1708 // Github Issue 19: Mnemonic is not sensitive to whitespace
1709 // https://github.com/iancoleman/bip39/issues/19
1710 function() {
1711 page.open(url, function(status) {
1712 // set the phrase
1713 var expected = "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1714 page.evaluate(function() {
1715 var doubleSpace = " ";
1716 $(".phrase").val("urge cat" + doubleSpace + "bid");
1717 $(".phrase").trigger("input");
1718 });
1719 waitForGenerate(function() {
1720 // Check the bip32 root key is correct
1721 var actual = page.evaluate(function() {
1722 return $(".root-key").val();
1723 });
1724 if (actual != expected) {
1725 console.log("Mnemonic is sensitive to whitespace");
1726 console.log("Expected: " + expected);
1727 console.log("Actual: " + actual);
1728 fail();
1729 }
1730 next();
1731 });
1732 });
1733 },
1734
1735 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1736 // https://github.com/iancoleman/bip39/issues/23
1737 function() {
1738 page.open(url, function(status) {
1739 // 1) and 2) set the phrase
1740 page.evaluate(function() {
1741 $(".phrase").val("abandon abandon ability").trigger("input");
1742 });
1743 waitForGenerate(function() {
1744 // 3) select bip32 tab
1745 page.evaluate(function() {
1746 $("#bip32-tab a").click();
1747 });
1748 waitForGenerate(function() {
1749 // 4) switch from bitcoin to litecoin
1750 page.evaluate(function() {
1751 $(".network").val("2").trigger("change");
1752 });
1753 waitForGenerate(function() {
1754 // 5) Check derivation path is displayed correctly
1755 var expected = "m/0/0";
1756 var actual = page.evaluate(function() {
1757 return $(".index:first").text();
1758 });
1759 if (actual != expected) {
1760 console.log("Github Issue 23 Part 1: derivation path display error");
1761 console.log("Expected: " + expected);
1762 console.log("Actual: " + actual);
1763 fail();
1764 }
1765 // 5) Check address is displayed correctly
1766 var expected = "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1767 var actual = page.evaluate(function() {
1768 return $(".address:first").text();
1769 });
1770 if (actual != expected) {
1771 console.log("Github Issue 23 Part 1: address display error");
1772 console.log("Expected: " + expected);
1773 console.log("Actual: " + actual);
1774 fail();
1775 }
1776 next();
1777 });
1778 });
1779 });
1780 });
1781 },
1782
1783 // Github Issue 23 Part 2: Coin selection in derivation path
1784 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1785 function() {
1786 page.open(url, function(status) {
1787 // set the phrase
1788 page.evaluate(function() {
1789 $(".phrase").val("abandon abandon ability").trigger("input");
1790 });
1791 waitForGenerate(function() {
1792 // switch from bitcoin to clam
1793 page.evaluate(function() {
1794 $(".network").val("9").trigger("change");
1795 });
1796 waitForGenerate(function() {
1797 // check derivation path is displayed correctly
1798 var expected = "m/44'/23'/0'/0/0";
1799 var actual = page.evaluate(function() {
1800 return $(".index:first").text();
1801 });
1802 if (actual != expected) {
1803 console.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1804 console.log("Expected: " + expected);
1805 console.log("Actual: " + actual);
1806 fail();
1807 }
1808 next();
1809 });
1810 });
1811 });
1812 },
1813
1814 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1815 // https://github.com/iancoleman/bip39/issues/26
1816 function() {
1817 page.open(url, function(status) {
1818 // 1) 2) and 3) set the root key
1819 page.evaluate(function() {
1820 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1821 });
1822 waitForGenerate(function() {
1823 // 4) switch from bitcoin to viacoin
1824 page.evaluate(function() {
1825 $(".network").val("6").trigger("change");
1826 });
1827 waitForGenerate(function() {
1828 // 5) ensure the derived address is correct
1829 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1830 var actual = page.evaluate(function() {
1831 return $(".address:first").text();
1832 });
1833 if (actual != expected) {
1834 console.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1835 console.log("Expected: " + expected);
1836 console.log("Actual: " + actual);
1837 fail();
1838 }
1839 next();
1840 });
1841 });
1842 });
1843 },
1844
1845 // Selecting a language with no existing phrase should generate a phrase in
1846 // that language.
1847 function() {
1848 page.open(url, function(status) {
1849 // Select a language
1850 // Need to manually simulate hash being set due to quirk between
1851 // 'click' event triggered by javascript vs triggered by mouse.
1852 // Perhaps look into page.sendEvent
1853 // http://phantomjs.org/api/webpage/method/send-event.html
1854 page.evaluate(function() {
1855 window.location.hash = "#japanese";
1856 $("a[href='#japanese']").trigger("click");
1857 });
1858 waitForGenerate(function() {
1859 // Check the mnemonic is in Japanese
1860 var phrase = page.evaluate(function() {
1861 return $(".phrase").val();
1862 });
1863 if (phrase.length <= 0) {
1864 console.log("No Japanese phrase generated");
1865 fail();
1866 }
1867 if (phrase.charCodeAt(0) < 128) {
1868 console.log("First character of Japanese phrase is ascii");
1869 console.log("Phrase: " + phrase);
1870 fail();
1871 }
1872 next();
1873 });
1874 });
1875 },
1876
1877 // Selecting a language with existing phrase should update the phrase to use
1878 // that language.
1879 function() {
1880 page.open(url, function(status) {
1881 // Set the phrase to an English phrase.
1882 page.evaluate(function() {
1883 $(".phrase").val("abandon abandon ability").trigger("input");
1884 });
1885 waitForGenerate(function() {
1886 // Change to Italian
1887 // Need to manually simulate hash being set due to quirk between
1888 // 'click' event triggered by javascript vs triggered by mouse.
1889 // Perhaps look into page.sendEvent
1890 // http://phantomjs.org/api/webpage/method/send-event.html
1891 page.evaluate(function() {
1892 window.location.hash = "#italian";
1893 $("a[href='#italian']").trigger("click");
1894 });
1895 waitForGenerate(function() {
1896 // Check only the language changes, not the phrase
1897 var expected = "abaco abaco abbaglio";
1898 var actual = page.evaluate(function() {
1899 return $(".phrase").val();
1900 });
1901 if (actual != expected) {
1902 console.log("Changing language with existing phrase");
1903 console.log("Expected: " + expected);
1904 console.log("Actual: " + actual);
1905 fail();
1906 }
1907 // Check the address is correct
1908 var expected = "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1909 var actual = page.evaluate(function() {
1910 return $(".address:first").text();
1911 });
1912 if (actual != expected) {
1913 console.log("Changing language generates incorrect address");
1914 console.log("Expected: " + expected);
1915 console.log("Actual: " + actual);
1916 fail();
1917 }
1918 next();
1919 });
1920 });
1921 });
1922 },
1923
1924 // Suggested replacement for erroneous word in non-English language
1925 function() {
1926 page.open(url, function(status) {
1927 // Set an incorrect phrase in Italian
1928 page.evaluate(function() {
1929 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
1930 });
1931 waitForFeedback(function() {
1932 // Check the suggestion is correct
1933 var feedback = page.evaluate(function() {
1934 return $(".feedback").text();
1935 });
1936 if (feedback.indexOf("did you mean abbaglio?") < 0) {
1937 console.log("Incorrect Italian word does not show suggested replacement");
1938 console.log("Error: " + error);
1939 fail();
1940 }
1941 next();
1942 });
1943 });
1944 },
1945
1946
1947 // Japanese word does not break across lines.
1948 // Point 2 from
1949 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1950 function() {
1951 page.open(url, function(status) {
1952 hasWordBreakCss = page.content.indexOf("word-break: keep-all;") > -1;
1953 if (!hasWordBreakCss) {
1954 console.log("Japanese words can break across lines mid-word");
1955 console.log("Check CSS for '.phrase { word-break: keep-all; }'");
1956 fail();
1957 }
1958 // Run the next test
1959 next();
1960 });
1961 },
1962
1963 // Language can be specified at page load using hash value in url
1964 function() {
1965 page.open(url, function(status) {
1966 // Set the page hash as if it were on a fresh page load
1967 page.evaluate(function() {
1968 window.location.hash = "#japanese";
1969 });
1970 // Generate a random phrase
1971 page.evaluate(function() {
1972 $(".generate").trigger("click");
1973 });
1974 waitForGenerate(function() {
1975 // Check the phrase is in Japanese
1976 var phrase = page.evaluate(function() {
1977 return $(".phrase").val();
1978 });
1979 if (phrase.length <= 0) {
1980 console.log("No phrase generated using url hash");
1981 fail();
1982 }
1983 if (phrase.charCodeAt(0) < 128) {
1984 console.log("Language not detected from url hash on page load.");
1985 console.log("Phrase: " + phrase);
1986 fail();
1987 }
1988 next();
1989 });
1990 });
1991 },
1992
1993 // Entropy unit tests
1994 function() {
1995 page.open(url, function(status) {
1996 var response = page.evaluate(function() {
1997 var e;
1998 // binary entropy is detected
1999 try {
2000 e = Entropy.fromString("01010101");
2001 if (e.base.str != "binary") {
2002 return "Binary entropy not detected correctly";
2003 }
2004 }
2005 catch (e) {
2006 return e.message;
2007 }
2008 // base6 entropy is detected
2009 try {
2010 e = Entropy.fromString("012345012345");
2011 if (e.base.str != "base 6") {
2012 return "base6 entropy not detected correctly";
2013 }
2014 }
2015 catch (e) {
2016 return e.message;
2017 }
2018 // dice entropy is detected
2019 try {
2020 e = Entropy.fromString("123456123456");
2021 if (e.base.str != "base 6 (dice)") {
2022 return "dice entropy not detected correctly";
2023 }
2024 }
2025 catch (e) {
2026 return e.message;
2027 }
2028 // base10 entropy is detected
2029 try {
2030 e = Entropy.fromString("0123456789");
2031 if (e.base.str != "base 10") {
2032 return "base10 entropy not detected correctly";
2033 }
2034 }
2035 catch (e) {
2036 return e.message;
2037 }
2038 // hex entropy is detected
2039 try {
2040 e = Entropy.fromString("0123456789ABCDEF");
2041 if (e.base.str != "hexadecimal") {
2042 return "hexadecimal entropy not detected correctly";
2043 }
2044 }
2045 catch (e) {
2046 return e.message;
2047 }
2048 // card entropy is detected
2049 try {
2050 e = Entropy.fromString("AC4DTHKS");
2051 if (e.base.str != "card") {
2052 return "card entropy not detected correctly";
2053 }
2054 }
2055 catch (e) {
2056 return e.message;
2057 }
2058 // entropy is case insensitive
2059 try {
2060 e = Entropy.fromString("aBcDeF");
2061 if (e.cleanStr != "aBcDeF") {
2062 return "Entropy should not be case sensitive";
2063 }
2064 }
2065 catch (e) {
2066 return e.message;
2067 }
2068 // dice entropy is converted to base6
2069 try {
2070 e = Entropy.fromString("123456");
2071 if (e.cleanStr != "123450") {
2072 return "Dice entropy is not automatically converted to base6";
2073 }
2074 }
2075 catch (e) {
2076 return e.message;
2077 }
2078 // dice entropy is preferred to base6 if ambiguous
2079 try {
2080 e = Entropy.fromString("12345");
2081 if (e.base.str != "base 6 (dice)") {
2082 return "dice not used as default over base 6";
2083 }
2084 }
2085 catch (e) {
2086 return e.message;
2087 }
2088 // unused characters are ignored
2089 try {
2090 e = Entropy.fromString("fghijkl");
2091 if (e.cleanStr != "f") {
2092 return "additional characters are not ignored";
2093 }
2094 }
2095 catch (e) {
2096 return e.message;
2097 }
2098 // the lowest base is used by default
2099 // 7 could be decimal or hexadecimal, but should be detected as decimal
2100 try {
2101 e = Entropy.fromString("7");
2102 if (e.base.str != "base 10") {
2103 return "lowest base is not used";
2104 }
2105 }
2106 catch (e) {
2107 return e.message;
2108 }
2109 // Leading zeros are retained
2110 try {
2111 e = Entropy.fromString("000A");
2112 if (e.cleanStr != "000A") {
2113 return "Leading zeros are not retained";
2114 }
2115 }
2116 catch (e) {
2117 return e.message;
2118 }
2119 // Leading zeros are correctly preserved for hex in binary string
2120 try {
2121 e = Entropy.fromString("2A");
2122 if (e.binaryStr != "00101010") {
2123 return "Hex leading zeros are not correct in binary";
2124 }
2125 }
2126 catch (e) {
2127 return e.message;
2128 }
2129 // Leading zeros for base 6 as binary string
2130 // 20 = 2 events at 2.58 bits per event = 5 bits
2131 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2132 // so it needs 1 bit of padding to be the right bit length
2133 try {
2134 e = Entropy.fromString("20");
2135 if (e.binaryStr != "01100") {
2136 return "Base 6 as binary has leading zeros";
2137 }
2138 }
2139 catch (e) {
2140 return e.message;
2141 }
2142 // Leading zeros for base 10 as binary string
2143 try {
2144 e = Entropy.fromString("17");
2145 if (e.binaryStr != "010001") {
2146 return "Base 10 as binary has leading zeros";
2147 }
2148 }
2149 catch (e) {
2150 return e.message;
2151 }
2152 // Leading zeros for card entropy as binary string
2153 try {
2154 e = Entropy.fromString("2c");
2155 if (e.binaryStr != "00001") {
2156 return "Card entropy as binary has leading zeros";
2157 }
2158 }
2159 catch (e) {
2160 return e.message;
2161 }
2162 // Keyboard mashing results in weak entropy
2163 // Despite being a long string, it's less than 30 bits of entropy
2164 try {
2165 e = Entropy.fromString("aj;se ifj; ask,dfv js;ifj");
2166 if (e.binaryStr.length >= 30) {
2167 return "Keyboard mashing should produce weak entropy";
2168 }
2169 }
2170 catch (e) {
2171 return e.message;
2172 }
2173 // Card entropy is used if every pair could be a card
2174 try {
2175 e = Entropy.fromString("4c3c2c");
2176 if (e.base.str != "card") {
2177 return "Card entropy not used if all pairs are cards";
2178 }
2179 }
2180 catch (e) {
2181 return e.message;
2182 }
2183 // Card entropy uses base 52
2184 // [ cards, binary ]
2185 try {
2186 var cards = [
2187 [ "ac", "00000" ],
2188 [ "acqs", "00000110001" ],
2189 [ "acks", "00000110010" ],
2190 [ "2cac", "00000110011" ],
2191 [ "2c", "00001" ],
2192 [ "3d", "01111" ],
2193 [ "4h", "11101" ],
2194 [ "5s", "101011" ],
2195 [ "6c", "00101" ],
2196 [ "7d", "10011" ],
2197 [ "8h", "100001" ],
2198 [ "9s", "101111" ],
2199 [ "tc", "01001" ],
2200 [ "jd", "10111" ],
2201 [ "qh", "100101" ],
2202 [ "ks", "110011" ],
2203 [ "ks2c", "101000101001" ],
2204 [ "KS2C", "101000101001" ],
2205 ];
2206 for (var i=0; i<cards.length; i++) {
2207 var card = cards[i][0];
2208 var result = cards[i][1];
2209 e = Entropy.fromString(card);
2210 console.log(e.binary + " " + result);
2211 if (e.binaryStr !== result) {
2212 return "card entropy not parsed correctly: " + result + " != " + e.binaryStr;
2213 }
2214 }
2215 }
2216 catch (e) {
2217 return e.message;
2218 }
2219 return "PASS";
2220 });
2221 if (response != "PASS") {
2222 console.log("Entropy unit tests");
2223 console.log(response);
2224 fail();
2225 };
2226 next();
2227 });
2228 },
2229
2230 // Entropy can be entered by the user
2231 function() {
2232 page.open(url, function(status) {
2233 expected = {
2234 mnemonic: "abandon abandon ability",
2235 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2236 }
2237 // use entropy
2238 page.evaluate(function() {
2239 $(".use-entropy").prop("checked", true).trigger("change");
2240 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2241 });
2242 // check the mnemonic is set and address is correct
2243 waitForGenerate(function() {
2244 var actual = page.evaluate(function() {
2245 return {
2246 address: $(".address:first").text(),
2247 mnemonic: $(".phrase").val(),
2248 }
2249 });
2250 if (actual.mnemonic != expected.mnemonic) {
2251 console.log("Entropy does not generate correct mnemonic");
2252 console.log("Expected: " + expected.mnemonic);
2253 console.log("Got: " + actual.mnemonic);
2254 fail();
2255 }
2256 if (actual.address != expected.address) {
2257 console.log("Entropy does not generate correct address");
2258 console.log("Expected: " + expected.address);
2259 console.log("Got: " + actual.address);
2260 fail();
2261 }
2262 next();
2263 });
2264 });
2265 },
2266
2267 // A warning about entropy is shown to the user, with additional information
2268 function() {
2269 page.open(url, function(status) {
2270 // get text content from entropy sections of page
2271 var hasWarning = page.evaluate(function() {
2272 var entropyText = $(".entropy-container").text();
2273 var warning = "mnemonic may be insecure";
2274 if (entropyText.indexOf(warning) == -1) {
2275 return false;
2276 }
2277 var readMoreText = $("#entropy-notes").parent().text();
2278 var goodSources = "flipping a fair coin, rolling a fair dice, noise measurements etc";
2279 if (readMoreText.indexOf(goodSources) == -1) {
2280 return false;
2281 }
2282 return true;
2283 });
2284 // check the warnings and information are shown
2285 if (!hasWarning) {
2286 console.log("Page does not contain warning about using own entropy");
2287 fail();
2288 }
2289 next();
2290 });
2291 },
2292
2293 // The types of entropy available are described to the user
2294 function() {
2295 page.open(url, function(status) {
2296 // get placeholder text for entropy field
2297 var placeholder = page.evaluate(function() {
2298 return $(".entropy").attr("placeholder");
2299 });
2300 var options = [
2301 "binary",
2302 "base 6",
2303 "dice",
2304 "base 10",
2305 "hexadecimal",
2306 "cards",
2307 ];
2308 for (var i=0; i<options.length; i++) {
2309 var option = options[i];
2310 if (placeholder.indexOf(option) == -1) {
2311 console.log("Available entropy type is not shown to user: " + option);
2312 fail();
2313 }
2314 }
2315 next();
2316 });
2317 },
2318
2319 // The actual entropy used is shown to the user
2320 function() {
2321 page.open(url, function(status) {
2322 // use entropy
2323 var badEntropySource = page.evaluate(function() {
2324 var entropy = "Not A Very Good Entropy Source At All";
2325 $(".use-entropy").prop("checked", true).trigger("change");
2326 $(".entropy").val(entropy).trigger("input");
2327 });
2328 // check the actual entropy being used is shown
2329 waitForEntropyFeedback(function() {
2330 var expectedText = "AedEceAA";
2331 var entropyText = page.evaluate(function() {
2332 return $(".entropy-container").text();
2333 });
2334 if (entropyText.indexOf(expectedText) == -1) {
2335 console.log("Actual entropy used is not shown");
2336 fail();
2337 }
2338 next();
2339 });
2340 });
2341 },
2342
2343 // Binary entropy can be entered
2344 function() {
2345 page.open(url, function(status) {
2346 // use entropy
2347 page.evaluate(function() {
2348 $(".use-entropy").prop("checked", true).trigger("change");
2349 $(".entropy").val("01").trigger("input");
2350 });
2351 // check the entropy is shown to be the correct type
2352 waitForEntropyFeedback(function() {
2353 var entropyText = page.evaluate(function() {
2354 return $(".entropy-container").text();
2355 });
2356 if (entropyText.indexOf("binary") == -1) {
2357 console.log("Binary entropy is not detected and presented to user");
2358 fail();
2359 }
2360 next();
2361 });
2362 });
2363 },
2364
2365 // Base 6 entropy can be entered
2366 function() {
2367 page.open(url, function(status) {
2368 // use entropy
2369 page.evaluate(function() {
2370 $(".use-entropy").prop("checked", true).trigger("change");
2371 $(".entropy").val("012345").trigger("input");
2372 });
2373 // check the entropy is shown to be the correct type
2374 waitForEntropyFeedback(function() {
2375 var entropyText = page.evaluate(function() {
2376 return $(".entropy-container").text();
2377 });
2378 if (entropyText.indexOf("base 6") == -1) {
2379 console.log("Base 6 entropy is not detected and presented to user");
2380 fail();
2381 }
2382 next();
2383 });
2384 });
2385 },
2386
2387 // Base 6 dice entropy can be entered
2388 function() {
2389 page.open(url, function(status) {
2390 // use entropy
2391 page.evaluate(function() {
2392 $(".use-entropy").prop("checked", true).trigger("change");
2393 $(".entropy").val("123456").trigger("input");
2394 });
2395 // check the entropy is shown to be the correct type
2396 waitForEntropyFeedback(function() {
2397 var entropyText = page.evaluate(function() {
2398 return $(".entropy-container").text();
2399 });
2400 if (entropyText.indexOf("dice") == -1) {
2401 console.log("Dice entropy is not detected and presented to user");
2402 fail();
2403 }
2404 next();
2405 });
2406 });
2407 },
2408
2409 // Base 10 entropy can be entered
2410 function() {
2411 page.open(url, function(status) {
2412 // use entropy
2413 page.evaluate(function() {
2414 $(".use-entropy").prop("checked", true).trigger("change");
2415 $(".entropy").val("789").trigger("input");
2416 });
2417 // check the entropy is shown to be the correct type
2418 waitForEntropyFeedback(function() {
2419 var entropyText = page.evaluate(function() {
2420 return $(".entropy-container").text();
2421 });
2422 if (entropyText.indexOf("base 10") == -1) {
2423 console.log("Base 10 entropy is not detected and presented to user");
2424 fail();
2425 }
2426 next();
2427 });
2428 });
2429 },
2430
2431 // Hexadecimal entropy can be entered
2432 function() {
2433 page.open(url, function(status) {
2434 // use entropy
2435 page.evaluate(function() {
2436 $(".use-entropy").prop("checked", true).trigger("change");
2437 $(".entropy").val("abcdef").trigger("input");
2438 });
2439 // check the entropy is shown to be the correct type
2440 waitForEntropyFeedback(function() {
2441 var entropyText = page.evaluate(function() {
2442 return $(".entropy-container").text();
2443 });
2444 if (entropyText.indexOf("hexadecimal") == -1) {
2445 console.log("Hexadecimal entropy is not detected and presented to user");
2446 fail();
2447 }
2448 next();
2449 });
2450 });
2451 },
2452
2453 // Dice entropy value is shown as the converted base 6 value
2454 function() {
2455 page.open(url, function(status) {
2456 // use entropy
2457 page.evaluate(function() {
2458 $(".use-entropy").prop("checked", true).trigger("change");
2459 $(".entropy").val("123456").trigger("input");
2460 });
2461 // check the entropy is shown as base 6, not as the original dice value
2462 waitForEntropyFeedback(function() {
2463 var entropyText = page.evaluate(function() {
2464 return $(".entropy-container").text();
2465 });
2466 if (entropyText.indexOf("123450") == -1) {
2467 console.log("Dice entropy is not shown to user as base 6 value");
2468 fail();
2469 }
2470 if (entropyText.indexOf("123456") > -1) {
2471 console.log("Dice entropy value is shown instead of true base 6 value");
2472 fail();
2473 }
2474 next();
2475 });
2476 });
2477 },
2478
2479 // The number of bits of entropy accumulated is shown
2480 function() {
2481 page.open(url, function(status) {
2482 //[ entropy, bits ]
2483 var tests = [
2484 [ "0000 0000 0000 0000 0000", "20" ],
2485 [ "0", "1" ],
2486 [ "0000", "4" ],
2487 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2488 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2489 [ "8", "4" ],
2490 [ "F", "4" ],
2491 [ "29", "6" ],
2492 [ "0A", "8" ],
2493 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2494 [ "2A", "8" ],
2495 [ "4A", "8" ],
2496 [ "8A", "8" ],
2497 [ "FA", "8" ],
2498 [ "000A", "16" ],
2499 [ "5555", "11" ],
2500 [ "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)
2501 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2502 [ "222F", "16" ],
2503 [ "FFFF", "16" ],
2504 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2505 [ "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
2506 ]
2507 // use entropy
2508 page.evaluate(function(e) {
2509 $(".use-entropy").prop("checked", true).trigger("change");
2510 });
2511 // Run each test
2512 var nextTest = function runNextTest(i) {
2513 var entropy = tests[i][0];
2514 var expected = tests[i][1];
2515 // set entropy
2516 page.evaluate(function(e) {
2517 $(".entropy").val(e).trigger("input");
2518 }, entropy);
2519 // check the number of bits of entropy is shown
2520 waitForEntropyFeedback(function() {
2521 var entropyText = page.evaluate(function() {
2522 return $(".entropy-container").text();
2523 });
2524 if (entropyText.replace(/\s/g,"").indexOf("Bits" + expected) == -1) {
2525 console.log("Accumulated entropy is not shown correctly for " + entropy);
2526 fail();
2527 }
2528 var isLastTest = i == tests.length - 1;
2529 if (isLastTest) {
2530 next();
2531 }
2532 else {
2533 runNextTest(i+1);
2534 }
2535 });
2536 }
2537 nextTest(0);
2538 });
2539 },
2540
2541 // There is feedback provided about the supplied entropy
2542 function() {
2543 page.open(url, function(status) {
2544 var tests = [
2545 {
2546 entropy: "A",
2547 filtered: "A",
2548 type: "hexadecimal",
2549 events: 1,
2550 bits: 4,
2551 words: 0,
2552 strength: "extremely weak",
2553 },
2554 {
2555 entropy: "AAAAAAAA",
2556 filtered: "AAAAAAAA",
2557 type: "hexadecimal",
2558 events: 8,
2559 bits: 32,
2560 words: 3,
2561 strength: "extremely weak",
2562 },
2563 {
2564 entropy: "AAAAAAAA B",
2565 filtered: "AAAAAAAAB",
2566 type: "hexadecimal",
2567 events: 9,
2568 bits: 36,
2569 words: 3,
2570 strength: "extremely weak",
2571 },
2572 {
2573 entropy: "AAAAAAAA BBBBBBBB",
2574 filtered: "AAAAAAAABBBBBBBB",
2575 type: "hexadecimal",
2576 events: 16,
2577 bits: 64,
2578 words: 6,
2579 strength: "very weak",
2580 },
2581 {
2582 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2583 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2584 type: "hexadecimal",
2585 events: 24,
2586 bits: 96,
2587 words: 9,
2588 strength: "weak",
2589 },
2590 {
2591 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2592 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2593 type: "hexadecimal",
2594 events: 32,
2595 bits: 128,
2596 words: 12,
2597 strength: "easily cracked",
2598 },
2599 {
2600 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2601 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2602 type: "hexadecimal",
2603 events: 32,
2604 bits: 128,
2605 words: 12,
2606 strength: "strong",
2607 },
2608 {
2609 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2610 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2611 type: "hexadecimal",
2612 events: 40,
2613 bits: 160,
2614 words: 15,
2615 strength: "very strong",
2616 },
2617 {
2618 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2619 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2620 type: "hexadecimal",
2621 events: 48,
2622 bits: 192,
2623 words: 18,
2624 strength: "extremely strong",
2625 },
2626 {
2627 entropy: "7d",
2628 type: "card",
2629 events: 1,
2630 bits: 5,
2631 words: 0,
2632 strength: "extremely weak",
2633 },
2634 {
2635 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2636 type: "card (full deck)",
2637 events: 52,
2638 bits: 225,
2639 words: 21,
2640 strength: "extremely strong",
2641 },
2642 {
2643 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2644 type: "card (full deck, 1 duplicate: 3d)",
2645 events: 53,
2646 bits: 231,
2647 words: 21,
2648 strength: "extremely strong",
2649 },
2650 {
2651 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2652 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2653 events: 53,
2654 bits: 231,
2655 words: 21,
2656 strength: "extremely strong",
2657 },
2658 {
2659 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2660 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2661 events: 53,
2662 bits: 242,
2663 words: 21,
2664 strength: "extremely strong",
2665 },
2666 // Next test was throwing uncaught error in zxcvbn
2667 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2668 {
2669 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2670 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2671 events: 104,
2672 bits: 451,
2673 words: 42,
2674 strength: "extremely strong",
2675 },
2676 // Case insensitivity to duplicate cards
2677 {
2678 entropy: "asAS",
2679 type: "card (1 duplicate: AS)",
2680 events: 2,
2681 bits: 12,
2682 words: 0,
2683 strength: "extremely weak",
2684 },
2685 {
2686 entropy: "ASas",
2687 type: "card (1 duplicate: as)",
2688 events: 2,
2689 bits: 12,
2690 words: 0,
2691 strength: "extremely weak",
2692 },
2693 // Missing cards are detected
2694 {
2695 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2696 type: "card (1 missing: 9C)",
2697 events: 51,
2698 bits: 225,
2699 words: 21,
2700 strength: "extremely strong",
2701 },
2702 {
2703 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2704 type: "card (2 missing: 9C 5D)",
2705 events: 50,
2706 bits: 224,
2707 words: 21,
2708 strength: "extremely strong",
2709 },
2710 {
2711 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2712 type: "card (4 missing: 9C 5D QD...)",
2713 events: 48,
2714 bits: 220,
2715 words: 18,
2716 strength: "extremely strong",
2717 },
2718 // More than six missing cards does not show message
2719 {
2720 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2721 type: "card",
2722 events: 45,
2723 bits: 213,
2724 words: 18,
2725 strength: "extremely strong",
2726 },
2727 ];
2728 // use entropy
2729 page.evaluate(function() {
2730 $(".use-entropy").prop("checked", true).trigger("change");
2731 });
2732 var nextTest = function runNextTest(i) {
2733 function getFeedbackError(expected, actual) {
2734 if ("filtered" in expected && actual.indexOf(expected.filtered) == -1) {
2735 return "Filtered value not in feedback";
2736 }
2737 if (actual.indexOf(expected.type) == -1) {
2738 return "Entropy type not in feedback";
2739 }
2740 if (actual.indexOf(expected.events) == -1) {
2741 return "Event count not in feedback";
2742 }
2743 if (actual.indexOf(expected.bits) == -1) {
2744 return "Bit count not in feedback";
2745 }
2746 if (actual.indexOf(expected.strength) == -1) {
2747 return "Strength not in feedback";
2748 }
2749 return false;
2750 }
2751 test = tests[i];
2752 page.evaluate(function(e) {
2753 $(".addresses").empty();
2754 $(".phrase").val("");
2755 $(".entropy").val(e).trigger("input");
2756 }, test.entropy);
2757 waitForEntropyFeedback(function() {
2758 var mnemonic = page.evaluate(function() {
2759 return $(".phrase").val();
2760 });
2761 // Check mnemonic length
2762 if (test.words == 0) {
2763 if (mnemonic.length > 0) {
2764 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2765 console.log("Mnemonic: " + mnemonic);
2766 fail();
2767 }
2768 }
2769 else {
2770 if (mnemonic.split(" ").length != test.words) {
2771 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2772 console.log("Mnemonic: " + mnemonic);
2773 fail();
2774 }
2775 }
2776 // check feedback
2777 var feedback = page.evaluate(function() {
2778 return $(".entropy-container").text();
2779 });
2780 var feedbackError = getFeedbackError(test, feedback);
2781 if (feedbackError) {
2782 console.log("Entropy feedback for " + test.entropy + " returned error");
2783 console.log(feedbackError);
2784 fail();
2785 }
2786 // Run next test
2787 var isLastTest = i == tests.length - 1;
2788 if (isLastTest) {
2789 next();
2790 }
2791 else {
2792 runNextTest(i+1);
2793 }
2794 });
2795 }
2796 nextTest(0);
2797 });
2798 },
2799
2800 // Entropy is truncated from the left
2801 function() {
2802 page.open(url, function(status) {
2803 var expected = "avocado zoo zone";
2804 // use entropy
2805 page.evaluate(function() {
2806 $(".use-entropy").prop("checked", true).trigger("change");
2807 var entropy = "00000000 00000000 00000000 00000000";
2808 entropy += "11111111 11111111 11111111 1111"; // Missing last byte
2809 $(".entropy").val(entropy).trigger("input");
2810 });
2811 // check the entropy is truncated from the right
2812 waitForGenerate(function() {
2813 var actual = page.evaluate(function() {
2814 return $(".phrase").val();
2815 });
2816 if (actual != expected) {
2817 console.log("Entropy is not truncated from the right");
2818 console.log("Expected: " + expected);
2819 console.log("Got: " + actual);
2820 fail();
2821 }
2822 next();
2823 });
2824 });
2825 },
2826
2827 // Very large entropy results in very long mnemonics
2828 function() {
2829 page.open(url, function(status) {
2830 // use entropy
2831 page.evaluate(function() {
2832 $(".use-entropy").prop("checked", true).trigger("change");
2833 var entropy = "";
2834 // Generate a very long entropy string
2835 for (var i=0; i<33; i++) {
2836 entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2837 }
2838 $(".entropy").val(entropy).trigger("input");
2839 });
2840 // check the mnemonic is very long
2841 waitForGenerate(function() {
2842 var wordCount = page.evaluate(function() {
2843 return $(".phrase").val().split(" ").length;
2844 });
2845 if (wordCount != 99) {
2846 console.log("Large entropy does not generate long mnemonic");
2847 console.log("Expected 99 words, got " + wordCount);
2848 fail();
2849 }
2850 next();
2851 });
2852 });
2853 },
2854
2855 // Is compatible with bip32jp entropy
2856 // https://bip32jp.github.io/english/index.html
2857 // NOTES:
2858 // Is incompatible with:
2859 // base 20
2860 function() {
2861 page.open(url, function(status) {
2862 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";
2863 // use entropy
2864 page.evaluate(function() {
2865 $(".use-entropy").prop("checked", true).trigger("change");
2866 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2867 $(".entropy").val(entropy).trigger("input");
2868 });
2869 // check the mnemonic matches the expected value from bip32jp
2870 waitForGenerate(function() {
2871 var actual = page.evaluate(function() {
2872 return $(".phrase").val();
2873 });
2874 if (actual != expected) {
2875 console.log("Mnemonic does not match bip32jp for base 6 entropy");
2876 console.log("Expected: " + expected);
2877 console.log("Got: " + actual);
2878 fail();
2879 }
2880 next();
2881 });
2882 });
2883 },
2884
2885 // Blank entropy does not generate mnemonic or addresses
2886 function() {
2887 page.open(url, function(status) {
2888 // use entropy
2889 page.evaluate(function() {
2890 $(".use-entropy").prop("checked", true).trigger("change");
2891 $(".entropy").val("").trigger("input");
2892 });
2893 waitForFeedback(function() {
2894 // check there is no mnemonic
2895 var phrase = page.evaluate(function() {
2896 return $(".phrase").val();
2897 });
2898 if (phrase != "") {
2899 console.log("Blank entropy does not result in blank mnemonic");
2900 console.log("Got: " + phrase);
2901 fail();
2902 }
2903 // check there are no addresses displayed
2904 var addresses = page.evaluate(function() {
2905 return $(".address").length;
2906 });
2907 if (addresses != 0) {
2908 console.log("Blank entropy does not result in zero addresses");
2909 fail();
2910 }
2911 // Check the feedback says 'blank entropy'
2912 var feedback = page.evaluate(function() {
2913 return $(".feedback").text();
2914 });
2915 if (feedback != "Blank entropy") {
2916 console.log("Blank entropy does not show feedback message");
2917 fail();
2918 }
2919 next();
2920 });
2921 });
2922 },
2923
2924 // Mnemonic length can be selected even for weak entropy
2925 function() {
2926 page.open(url, function(status) {
2927 // use entropy
2928 page.evaluate(function() {
2929 $(".use-entropy").prop("checked", true).trigger("change");
2930 $(".entropy").val("012345");
2931 $(".mnemonic-length").val("18").trigger("change");
2932 });
2933 // check the mnemonic is the correct length
2934 waitForGenerate(function() {
2935 var phrase = page.evaluate(function() {
2936 return $(".phrase").val();
2937 });
2938 var numberOfWords = phrase.split(/\s/g).length;
2939 if (numberOfWords != 18) {
2940 console.log("Weak entropy cannot be overridden to give 18 word mnemonic");
2941 console.log(phrase);
2942 fail();
2943 }
2944 next();
2945 });
2946 });
2947 },
2948
2949 // If you wish to add more tests, do so here...
2950
2951 // Here is a blank test template
2952 /*
2953
2954 function() {
2955 page.open(url, function(status) {
2956 // Do something on the page
2957 page.evaluate(function() {
2958 $(".phrase").val("abandon abandon ability").trigger("input");
2959 });
2960 waitForGenerate(function() {
2961 // Check the result of doing the thing
2962 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
2963 var actual = page.evaluate(function() {
2964 return $(".address:first").text();
2965 });
2966 if (actual != expected) {
2967 console.log("A specific message about what failed");
2968 console.log("Expected: " + expected);
2969 console.log("Actual: " + actual);
2970 fail();
2971 }
2972 // Run the next test
2973 next();
2974 });
2975 });
2976 },
2977
2978 */
2979
2980 ];
2981
2982 console.log("Running tests...");
2983 tests = shuffle(tests);
2984 next();