]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
No leading zeros for first char unless hex
[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 = 5000;
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-error").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 var feedback = $(".entropy-error");
96 if (feedback.css("display") == "none") {
97 return "";
98 }
99 return feedback.text();
100 });
101 var hasFinished = feedback != origFeedback;
102 if (hasFinished) {
103 fn();
104 }
105 else {
106 setTimeout(keepWaiting, 100);
107 }
108 }
109 wait();
110 }
111
112 function next() {
113 if (tests.length > 0) {
114 var testsStr = tests.length == 1 ? "test" : "tests";
115 console.log(tests.length + " " + testsStr + " remaining");
116 tests.shift()();
117 }
118 else {
119 console.log("Finished with 0 failures");
120 phantom.exit();
121 }
122 }
123
124 /**
125 * Randomize array element order in-place.
126 * Using Durstenfeld shuffle algorithm.
127 * See http://stackoverflow.com/a/12646864
128 */
129 function shuffle(array) {
130 for (var i = array.length - 1; i > 0; i--) {
131 var j = Math.floor(Math.random() * (i + 1));
132 var temp = array[i];
133 array[i] = array[j];
134 array[j] = temp;
135 }
136 return array;
137 }
138
139 tests = [
140
141 // Page loads with status of 'success'
142 function() {
143 page.open(url, function(status) {
144 if (status != "success") {
145 console.log("Page did not load with status 'success'");
146 fail();
147 }
148 next();
149 });
150 },
151
152 // Page has text
153 function() {
154 page.open(url, function(status) {
155 var content = page.evaluate(function() {
156 return document.body.textContent.trim();
157 });
158 if (!content) {
159 console.log("Page does not have text");
160 fail();
161 }
162 next();
163 });
164 },
165
166 // Entering mnemonic generates addresses
167 function() {
168 page.open(url, function(status) {
169 // set the phrase
170 page.evaluate(function() {
171 $(".phrase").val("abandon abandon ability").trigger("input");
172 });
173 // get the address
174 waitForGenerate(function() {
175 var addressCount = page.evaluate(function() {
176 return $(".address").length;
177 });
178 if (addressCount != 20) {
179 console.log("Mnemonic did not generate addresses");
180 console.log("Expected: " + expected);
181 console.log("Got: " + actual);
182 fail();
183 }
184 next();
185 });
186 });
187 },
188
189 // Random button generates random mnemonic
190 function() {
191 page.open(url, function(status) {
192 // check initial phrase is empty
193 var phrase = page.evaluate(function() {
194 return $(".phrase").text();
195 });
196 if (phrase != "") {
197 console.log("Initial phrase is not blank");
198 fail();
199 }
200 // press the 'generate' button
201 page.evaluate(function() {
202 $(".generate").click();
203 });
204 // get the new phrase
205 waitForGenerate(function() {
206 var phrase = page.evaluate(function() {
207 return $(".phrase").val();
208 });
209 if (phrase.length <= 0) {
210 console.log("Phrase not generated by pressing button");
211 fail();
212 }
213 next();
214 });
215 });
216 },
217
218 // Mnemonic length can be customized
219 function() {
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);
226 });
227 // press the 'generate' button
228 page.evaluate(function() {
229 $(".generate").click();
230 });
231 // check the new phrase is six words long
232 waitForGenerate(function() {
233 var actualLength = page.evaluate(function() {
234 var words = $(".phrase").val().split(" ");
235 return words.length;
236 });
237 if (actualLength != expectedLength) {
238 console.log("Phrase not generated with correct length");
239 console.log("Expected: " + expectedLength);
240 console.log("Actual: " + actualLength);
241 fail();
242 }
243 next();
244 });
245 });
246 },
247
248 // Passphrase can be set
249 function() {
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");
256 });
257 // check the address is generated correctly
258 waitForGenerate(function() {
259 var actual = page.evaluate(function() {
260 return $(".address:first").text();
261 });
262 if (actual != expected) {
263 console.log("Passphrase results in wrong address");
264 console.log("Expected: " + expected);
265 console.log("Actual: " + actual);
266 fail();
267 }
268 next();
269 });
270 });
271 },
272
273 // Network can be set to bitcoin testnet
274 function() {
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");
284 });
285 // check the address is generated correctly
286 waitForGenerate(function() {
287 var actual = page.evaluate(function() {
288 return $(".address:first").text();
289 });
290 if (actual != expected) {
291 console.log("Bitcoin testnet address is incorrect");
292 console.log("Expected: " + expected);
293 console.log("Actual: " + actual);
294 fail();
295 }
296 next();
297 });
298 });
299 },
300
301 // Network can be set to litecoin
302 function() {
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");
312 });
313 // check the address is generated correctly
314 waitForGenerate(function() {
315 var actual = page.evaluate(function() {
316 return $(".address:first").text();
317 });
318 if (actual != expected) {
319 console.log("Litecoin address is incorrect");
320 console.log("Expected: " + expected);
321 console.log("Actual: " + actual);
322 fail();
323 }
324 next();
325 });
326 });
327 },
328
329 // Network can be set to dogecoin
330 function() {
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");
340 });
341 // check the address is generated correctly
342 waitForGenerate(function() {
343 var actual = page.evaluate(function() {
344 return $(".address:first").text();
345 });
346 if (actual != expected) {
347 console.log("Dogecoin address is incorrect");
348 console.log("Expected: " + expected);
349 console.log("Actual: " + actual);
350 fail();
351 }
352 next();
353 });
354 });
355 },
356
357 // Network can be set to shadowcash
358 function() {
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");
368 });
369 // check the address is generated correctly
370 waitForGenerate(function() {
371 var actual = page.evaluate(function() {
372 return $(".address:first").text();
373 });
374 if (actual != expected) {
375 console.log("Shadowcash address is incorrect");
376 console.log("Expected: " + expected);
377 console.log("Actual: " + actual);
378 fail();
379 }
380 next();
381 });
382 });
383 },
384
385 // Network can be set to shadowcash testnet
386 function() {
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");
396 });
397 // check the address is generated correctly
398 waitForGenerate(function() {
399 var actual = page.evaluate(function() {
400 return $(".address:first").text();
401 });
402 if (actual != expected) {
403 console.log("Shadowcash testnet address is incorrect");
404 console.log("Expected: " + expected);
405 console.log("Actual: " + actual);
406 fail();
407 }
408 next();
409 });
410 });
411 },
412
413 // Network can be set to viacoin
414 function() {
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");
424 });
425 // check the address is generated correctly
426 waitForGenerate(function() {
427 var actual = page.evaluate(function() {
428 return $(".address:first").text();
429 });
430 if (actual != expected) {
431 console.log("Viacoin address is incorrect");
432 console.log("Expected: " + expected);
433 console.log("Actual: " + actual);
434 fail();
435 }
436 next();
437 });
438 });
439 },
440
441 // Network can be set to viacoin testnet
442 function() {
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");
452 });
453 // check the address is generated correctly
454 waitForGenerate(function() {
455 var actual = page.evaluate(function() {
456 return $(".address:first").text();
457 });
458 if (actual != expected) {
459 console.log("Viacoin testnet address is incorrect");
460 console.log("Expected: " + expected);
461 console.log("Actual: " + actual);
462 fail();
463 }
464 next();
465 });
466 });
467 },
468
469 // Network can be set to jumbucks
470 function() {
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");
480 });
481 // check the address is generated correctly
482 waitForGenerate(function() {
483 var actual = page.evaluate(function() {
484 return $(".address:first").text();
485 });
486 if (actual != expected) {
487 console.log("Jumbucks address is incorrect");
488 console.log("Expected: " + expected);
489 console.log("Actual: " + actual);
490 fail();
491 }
492 next();
493 });
494 });
495 },
496
497 // Network can be set to clam
498 function() {
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");
508 });
509 // check the address is generated correctly
510 waitForGenerate(function() {
511 var actual = page.evaluate(function() {
512 return $(".address:first").text();
513 });
514 if (actual != expected) {
515 console.log("CLAM address is incorrect");
516 console.log("Expected: " + expected);
517 console.log("Actual: " + actual);
518 fail();
519 }
520 next();
521 });
522 });
523 },
524
525 // Network can be set to dash
526 function() {
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");
536 });
537 // check the address is generated correctly
538 waitForGenerate(function() {
539 var actual = page.evaluate(function() {
540 return $(".address:first").text();
541 });
542 if (actual != expected) {
543 console.log("DASH address is incorrect");
544 console.log("Expected: " + expected);
545 console.log("Actual: " + actual);
546 fail();
547 }
548 next();
549 });
550 });
551 },
552
553 // Network can be set to namecoin
554 function() {
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");
564 });
565 // check the address is generated correctly
566 waitForGenerate(function() {
567 var actual = page.evaluate(function() {
568 return $(".address:first").text();
569 });
570 if (actual != expected) {
571 console.log("Namecoin address is incorrect");
572 console.log("Expected: " + expected);
573 console.log("Actual: " + actual);
574 fail();
575 }
576 next();
577 });
578 });
579 },
580
581 // Network can be set to peercoin
582 function() {
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");
592 });
593 // check the address is generated correctly
594 waitForGenerate(function() {
595 var actual = page.evaluate(function() {
596 return $(".address:first").text();
597 });
598 if (actual != expected) {
599 console.log("Peercoin address is incorrect");
600 console.log("Expected: " + expected);
601 console.log("Actual: " + actual);
602 fail();
603 }
604 next();
605 });
606 });
607 },
608
609 // BIP39 seed is set from phrase
610 function() {
611 page.open(url, function(status) {
612 // set the phrase
613 var expected = "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
614 page.evaluate(function() {
615 $(".phrase").val("abandon abandon ability");
616 $(".phrase").trigger("input");
617 });
618 // check the address is generated correctly
619 waitForGenerate(function() {
620 var actual = page.evaluate(function() {
621 return $(".seed").val();
622 });
623 if (actual != expected) {
624 console.log("BIP39 seed is incorrectly generated from mnemonic");
625 console.log("Expected: " + expected);
626 console.log("Actual: " + actual);
627 fail();
628 }
629 next();
630 });
631 });
632 },
633
634 // BIP32 root key is set from phrase
635 function() {
636 page.open(url, function(status) {
637 // set the phrase
638 var expected = "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
639 page.evaluate(function() {
640 $(".phrase").val("abandon abandon ability");
641 $(".phrase").trigger("input");
642 });
643 // check the address is generated correctly
644 waitForGenerate(function() {
645 var actual = page.evaluate(function() {
646 return $(".root-key").val();
647 });
648 if (actual != expected) {
649 console.log("Root key is incorrectly generated from mnemonic");
650 console.log("Expected: " + expected);
651 console.log("Actual: " + actual);
652 fail();
653 }
654 next();
655 });
656 });
657 },
658
659 // Tabs show correct addresses when changed
660 function() {
661 page.open(url, function(status) {
662 // set the phrase
663 var expected = "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
664 page.evaluate(function() {
665 $(".phrase").val("abandon abandon ability");
666 $(".phrase").trigger("input");
667 });
668 // change tabs
669 waitForGenerate(function() {
670 page.evaluate(function() {
671 $("#bip32-tab a").click();
672 });
673 // check the address is generated correctly
674 waitForGenerate(function() {
675 var actual = page.evaluate(function() {
676 return $(".address:first").text();
677 });
678 if (actual != expected) {
679 console.log("Clicking tab generates incorrect address");
680 console.log("Expected: " + expected);
681 console.log("Actual: " + actual);
682 fail();
683 }
684 next();
685 });
686 });
687 });
688 },
689
690 // BIP44 derivation path is shown
691 function() {
692 page.open(url, function(status) {
693 // set the phrase
694 var expected = "m/44'/0'/0'/0";
695 page.evaluate(function() {
696 $(".phrase").val("abandon abandon ability");
697 $(".phrase").trigger("input");
698 });
699 // check the derivation path of the first address
700 waitForGenerate(function() {
701 var actual = page.evaluate(function() {
702 return $("#bip44 .path").val();
703 });
704 if (actual != expected) {
705 console.log("BIP44 derivation path is incorrect");
706 console.log("Expected: " + expected);
707 console.log("Actual: " + actual);
708 fail();
709 }
710 next();
711 });
712 });
713 },
714
715 // BIP44 extended private key is shown
716 function() {
717 page.open(url, function(status) {
718 // set the phrase
719 var expected = "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
720 page.evaluate(function() {
721 $(".phrase").val("abandon abandon ability");
722 $(".phrase").trigger("input");
723 });
724 // check the BIP44 extended private key
725 waitForGenerate(function() {
726 var actual = page.evaluate(function() {
727 return $(".extended-priv-key").val();
728 });
729 if (actual != expected) {
730 console.log("BIP44 extended private key is incorrect");
731 console.log("Expected: " + expected);
732 console.log("Actual: " + actual);
733 fail();
734 }
735 next();
736 });
737 });
738 },
739
740 // BIP44 extended public key is shown
741 function() {
742 page.open(url, function(status) {
743 // set the phrase
744 var expected = "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
745 page.evaluate(function() {
746 $(".phrase").val("abandon abandon ability");
747 $(".phrase").trigger("input");
748 });
749 // check the BIP44 extended public key
750 waitForGenerate(function() {
751 var actual = page.evaluate(function() {
752 return $(".extended-pub-key").val();
753 });
754 if (actual != expected) {
755 console.log("BIP44 extended public key is incorrect");
756 console.log("Expected: " + expected);
757 console.log("Actual: " + actual);
758 fail();
759 }
760 next();
761 });
762 });
763 },
764
765 // BIP44 purpose field changes address list
766 function() {
767 page.open(url, function(status) {
768 // set the phrase
769 var expected = "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
770 page.evaluate(function() {
771 $(".phrase").val("abandon abandon ability");
772 $(".phrase").trigger("input");
773 });
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");
779 });
780 waitForGenerate(function() {
781 // check the address for the new derivation path
782 var actual = page.evaluate(function() {
783 return $(".address:first").text();
784 });
785 if (actual != expected) {
786 console.log("BIP44 purpose field generates incorrect address");
787 console.log("Expected: " + expected);
788 console.log("Actual: " + actual);
789 fail();
790 }
791 next();
792 });
793 });
794 });
795 },
796
797 // BIP44 coin field changes address list
798 function() {
799 page.open(url, function(status) {
800 // set the phrase
801 var expected = "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
802 page.evaluate(function() {
803 $(".phrase").val("abandon abandon ability");
804 $(".phrase").trigger("input");
805 });
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");
811 });
812 waitForGenerate(function() {
813 // check the address for the new derivation path
814 var actual = page.evaluate(function() {
815 return $(".address:first").text();
816 });
817 if (actual != expected) {
818 console.log("BIP44 coin field generates incorrect address");
819 console.log("Expected: " + expected);
820 console.log("Actual: " + actual);
821 fail();
822 }
823 next();
824 });
825 });
826 });
827 },
828
829 // BIP44 account field changes address list
830 function() {
831 page.open(url, function(status) {
832 // set the phrase
833 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
834 page.evaluate(function() {
835 $(".phrase").val("abandon abandon ability");
836 $(".phrase").trigger("input");
837 });
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");
843 });
844 waitForGenerate(function() {
845 // check the address for the new derivation path
846 var actual = page.evaluate(function() {
847 return $(".address:first").text();
848 });
849 if (actual != expected) {
850 console.log("BIP44 account field generates incorrect address");
851 console.log("Expected: " + expected);
852 console.log("Actual: " + actual);
853 fail();
854 }
855 next();
856 });
857 });
858 });
859 },
860
861 // BIP44 change field changes address list
862 function() {
863 page.open(url, function(status) {
864 // set the phrase
865 var expected = "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
866 page.evaluate(function() {
867 $(".phrase").val("abandon abandon ability");
868 $(".phrase").trigger("input");
869 });
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");
875 });
876 waitForGenerate(function() {
877 // check the address for the new derivation path
878 var actual = page.evaluate(function() {
879 return $(".address:first").text();
880 });
881 if (actual != expected) {
882 console.log("BIP44 change field generates incorrect address");
883 console.log("Expected: " + expected);
884 console.log("Actual: " + actual);
885 fail();
886 }
887 next();
888 });
889 });
890 });
891 },
892
893 // BIP32 derivation path can be set
894 function() {
895 page.open(url, function(status) {
896 // set the phrase
897 var expected = "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
898 page.evaluate(function() {
899 $(".phrase").val("abandon abandon ability");
900 $(".phrase").trigger("input");
901 });
902 // change tabs
903 waitForGenerate(function() {
904 page.evaluate(function() {
905 $("#bip32-tab a").click();
906 });
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");
912 });
913 // check the address is generated correctly
914 waitForGenerate(function() {
915 var actual = page.evaluate(function() {
916 return $(".address:first").text();
917 });
918 if (actual != expected) {
919 console.log("Custom BIP32 path generates incorrect address");
920 console.log("Expected: " + expected);
921 console.log("Actual: " + actual);
922 fail();
923 }
924 next();
925 });
926 });
927 });
928 });
929 },
930
931 // BIP32 can use hardened derivation paths
932 function() {
933 page.open(url, function(status) {
934 // set the phrase
935 var expected = "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
936 page.evaluate(function() {
937 $(".phrase").val("abandon abandon ability");
938 $(".phrase").trigger("input");
939 });
940 // change tabs
941 waitForGenerate(function() {
942 page.evaluate(function() {
943 $("#bip32-tab a").click();
944 });
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");
950 });
951 // check the address is generated correctly
952 waitForGenerate(function() {
953 var actual = page.evaluate(function() {
954 return $(".address:first").text();
955 });
956 if (actual != expected) {
957 console.log("Hardened BIP32 path generates incorrect address");
958 console.log("Expected: " + expected);
959 console.log("Actual: " + actual);
960 fail();
961 }
962 next();
963 });
964 });
965 });
966 });
967 },
968
969 // BIP32 extended private key is shown
970 function() {
971 page.open(url, function(status) {
972 // set the phrase
973 var expected = "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
974 page.evaluate(function() {
975 $(".phrase").val("abandon abandon ability");
976 $(".phrase").trigger("input");
977 });
978 // change tabs
979 waitForGenerate(function() {
980 page.evaluate(function() {
981 $("#bip32-tab a").click();
982 });
983 // check the extended private key is generated correctly
984 waitForGenerate(function() {
985 var actual = page.evaluate(function() {
986 return $(".extended-priv-key").val();
987 });
988 if (actual != expected) {
989 console.log("BIP32 extended private key is incorrect");
990 console.log("Expected: " + expected);
991 console.log("Actual: " + actual);
992 fail();
993 }
994 next();
995 });
996 });
997 });
998 },
999
1000 // BIP32 extended public key is shown
1001 function() {
1002 page.open(url, function(status) {
1003 // set the phrase
1004 var expected = "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1005 page.evaluate(function() {
1006 $(".phrase").val("abandon abandon ability");
1007 $(".phrase").trigger("input");
1008 });
1009 // change tabs
1010 waitForGenerate(function() {
1011 page.evaluate(function() {
1012 $("#bip32-tab a").click();
1013 });
1014 // check the extended public key is generated correctly
1015 waitForGenerate(function() {
1016 var actual = page.evaluate(function() {
1017 return $(".extended-pub-key").val();
1018 });
1019 if (actual != expected) {
1020 console.log("BIP32 extended public key is incorrect");
1021 console.log("Expected: " + expected);
1022 console.log("Actual: " + actual);
1023 fail();
1024 }
1025 next();
1026 });
1027 });
1028 });
1029 },
1030
1031 // Derivation path is shown in table
1032 function() {
1033 page.open(url, function(status) {
1034 // set the phrase
1035 var expected = "m/44'/0'/0'/0/0";
1036 page.evaluate(function() {
1037 $(".phrase").val("abandon abandon ability");
1038 $(".phrase").trigger("input");
1039 });
1040 // check for derivation path in table
1041 waitForGenerate(function() {
1042 var actual = page.evaluate(function() {
1043 return $(".index:first").text();
1044 });
1045 if (actual != expected) {
1046 console.log("Derivation path shown incorrectly in table");
1047 console.log("Expected: " + expected);
1048 console.log("Actual: " + actual);
1049 fail();
1050 }
1051 next();
1052 });
1053 });
1054 },
1055
1056 // Derivation path for address can be hardened
1057 function() {
1058 page.open(url, function(status) {
1059 // set the phrase
1060 var expected = "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1061 page.evaluate(function() {
1062 $(".phrase").val("abandon abandon ability");
1063 $(".phrase").trigger("input");
1064 });
1065 // change tabs
1066 waitForGenerate(function() {
1067 page.evaluate(function() {
1068 $("#bip32-tab a").click();
1069 });
1070 waitForGenerate(function() {
1071 // select the hardened addresses option
1072 page.evaluate(function() {
1073 $(".hardened-addresses").prop("checked", true);
1074 $(".hardened-addresses").trigger("change");
1075 });
1076 waitForGenerate(function() {
1077 // check the generated address is hardened
1078 var actual = page.evaluate(function() {
1079 return $(".address:first").text();
1080 });
1081 if (actual != expected) {
1082 console.log("Hardened address is incorrect");
1083 console.log("Expected: " + expected);
1084 console.log("Actual: " + actual);
1085 fail();
1086 }
1087 next();
1088 });
1089 });
1090 });
1091 });
1092 },
1093
1094 // Derivation path visibility can be toggled
1095 function() {
1096 page.open(url, function(status) {
1097 // set the phrase
1098 page.evaluate(function() {
1099 $(".phrase").val("abandon abandon ability");
1100 $(".phrase").trigger("input");
1101 });
1102 waitForGenerate(function() {
1103 // toggle path visibility
1104 page.evaluate(function() {
1105 $(".index-toggle").click();
1106 });
1107 // check the path is not visible
1108 var isInvisible = page.evaluate(function() {
1109 return $(".index:first span").hasClass("invisible");
1110 });
1111 if (!isInvisible) {
1112 console.log("Toggled derivation path is visible");
1113 fail();
1114 }
1115 next();
1116 });
1117 });
1118 },
1119
1120 // Address is shown
1121 function() {
1122 page.open(url, function(status) {
1123 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1124 // set the phrase
1125 page.evaluate(function() {
1126 $(".phrase").val("abandon abandon ability").trigger("input");
1127 });
1128 // get the address
1129 waitForGenerate(function() {
1130 var actual = page.evaluate(function() {
1131 return $(".address:first").text();
1132 });
1133 if (actual != expected) {
1134 console.log("Address is not shown");
1135 console.log("Expected: " + expected);
1136 console.log("Got: " + actual);
1137 fail();
1138 }
1139 next();
1140 });
1141 });
1142 },
1143
1144 // Addresses are shown in order of derivation path
1145 function() {
1146 page.open(url, function(status) {
1147 // set the phrase
1148 page.evaluate(function() {
1149 $(".phrase").val("abandon abandon ability").trigger("input");
1150 });
1151 // get the derivation paths
1152 waitForGenerate(function() {
1153 var paths = page.evaluate(function() {
1154 return $(".index").map(function(i, e) {
1155 return $(e).text();
1156 });
1157 });
1158 if (paths.length != 20) {
1159 console.log("Total paths is less than expected: " + paths.length);
1160 fail();
1161 }
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);
1169 fail();
1170 }
1171 }
1172 next();
1173 });
1174 });
1175 },
1176
1177 // Address visibility can be toggled
1178 function() {
1179 page.open(url, function(status) {
1180 // set the phrase
1181 page.evaluate(function() {
1182 $(".phrase").val("abandon abandon ability");
1183 $(".phrase").trigger("input");
1184 });
1185 waitForGenerate(function() {
1186 // toggle address visibility
1187 page.evaluate(function() {
1188 $(".address-toggle").click();
1189 });
1190 // check the address is not visible
1191 var isInvisible = page.evaluate(function() {
1192 return $(".address:first span").hasClass("invisible");
1193 });
1194 if (!isInvisible) {
1195 console.log("Toggled address is visible");
1196 fail();
1197 }
1198 next();
1199 });
1200 });
1201 },
1202
1203 // Public key is shown
1204 function() {
1205 page.open(url, function(status) {
1206 var expected = "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1207 // set the phrase
1208 page.evaluate(function() {
1209 $(".phrase").val("abandon abandon ability").trigger("input");
1210 });
1211 // get the address
1212 waitForGenerate(function() {
1213 var actual = page.evaluate(function() {
1214 return $(".pubkey:first").text();
1215 });
1216 if (actual != expected) {
1217 console.log("Public key is not shown");
1218 console.log("Expected: " + expected);
1219 console.log("Got: " + actual);
1220 fail();
1221 }
1222 next();
1223 });
1224 });
1225 },
1226
1227 // Public key visibility can be toggled
1228 function() {
1229 page.open(url, function(status) {
1230 // set the phrase
1231 page.evaluate(function() {
1232 $(".phrase").val("abandon abandon ability");
1233 $(".phrase").trigger("input");
1234 });
1235 waitForGenerate(function() {
1236 // toggle public key visibility
1237 page.evaluate(function() {
1238 $(".public-key-toggle").click();
1239 });
1240 // check the public key is not visible
1241 var isInvisible = page.evaluate(function() {
1242 return $(".pubkey:first span").hasClass("invisible");
1243 });
1244 if (!isInvisible) {
1245 console.log("Toggled public key is visible");
1246 fail();
1247 }
1248 next();
1249 });
1250 });
1251 },
1252
1253 // Private key is shown
1254 function() {
1255 page.open(url, function(status) {
1256 var expected = "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1257 // set the phrase
1258 page.evaluate(function() {
1259 $(".phrase").val("abandon abandon ability").trigger("input");
1260 });
1261 // get the address
1262 waitForGenerate(function() {
1263 var actual = page.evaluate(function() {
1264 return $(".privkey:first").text();
1265 });
1266 if (actual != expected) {
1267 console.log("Private key is not shown");
1268 console.log("Expected: " + expected);
1269 console.log("Got: " + actual);
1270 fail();
1271 }
1272 next();
1273 });
1274 });
1275 },
1276
1277 // Private key visibility can be toggled
1278 function() {
1279 page.open(url, function(status) {
1280 // set the phrase
1281 page.evaluate(function() {
1282 $(".phrase").val("abandon abandon ability");
1283 $(".phrase").trigger("input");
1284 });
1285 waitForGenerate(function() {
1286 // toggle private key visibility
1287 page.evaluate(function() {
1288 $(".private-key-toggle").click();
1289 });
1290 // check the private key is not visible
1291 var isInvisible = page.evaluate(function() {
1292 return $(".privkey:first span").hasClass("invisible");
1293 });
1294 if (!isInvisible) {
1295 console.log("Toggled private key is visible");
1296 fail();
1297 }
1298 next();
1299 });
1300 });
1301 },
1302
1303 // More addresses can be generated
1304 function() {
1305 page.open(url, function(status) {
1306 // set the phrase
1307 page.evaluate(function() {
1308 $(".phrase").val("abandon abandon ability");
1309 $(".phrase").trigger("input");
1310 });
1311 waitForGenerate(function() {
1312 // generate more addresses
1313 page.evaluate(function() {
1314 $(".more").click();
1315 });
1316 waitForGenerate(function() {
1317 // check there are more addresses
1318 var addressCount = page.evaluate(function() {
1319 return $(".address").length;
1320 });
1321 if (addressCount != 40) {
1322 console.log("More addresses cannot be generated");
1323 fail();
1324 }
1325 next();
1326 });
1327 });
1328 });
1329 },
1330
1331 // A custom number of additional addresses can be generated
1332 function() {
1333 page.open(url, function(status) {
1334 // set the phrase
1335 page.evaluate(function() {
1336 $(".phrase").val("abandon abandon ability");
1337 $(".phrase").trigger("input");
1338 });
1339 waitForGenerate(function() {
1340 // get the current number of addresses
1341 var oldAddressCount = page.evaluate(function() {
1342 return $(".address").length;
1343 });
1344 // set a custom number of additional addresses
1345 page.evaluate(function() {
1346 $(".rows-to-add").val(1);
1347 });
1348 // generate more addresses
1349 page.evaluate(function() {
1350 $(".more").click();
1351 });
1352 waitForGenerate(function() {
1353 // check there are the correct number of addresses
1354 var newAddressCount = page.evaluate(function() {
1355 return $(".address").length;
1356 });
1357 if (newAddressCount - oldAddressCount != 1) {
1358 console.log("Number of additional addresses cannot be customized");
1359 console.log(newAddressCount)
1360 console.log(oldAddressCount)
1361 fail();
1362 }
1363 next();
1364 });
1365 });
1366 });
1367 },
1368
1369 // Additional addresses are shown in order of derivation path
1370 function() {
1371 page.open(url, function(status) {
1372 // set the phrase
1373 page.evaluate(function() {
1374 $(".phrase").val("abandon abandon ability").trigger("input");
1375 });
1376 waitForGenerate(function() {
1377 // generate more addresses
1378 page.evaluate(function() {
1379 $(".more").click();
1380 });
1381 // get the derivation paths
1382 waitForGenerate(function() {
1383 var paths = page.evaluate(function() {
1384 return $(".index").map(function(i, e) {
1385 return $(e).text();
1386 });
1387 });
1388 if (paths.length != 40) {
1389 console.log("Total additional paths is less than expected: " + paths.length);
1390 fail();
1391 }
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);
1399 fail();
1400 }
1401 }
1402 next();
1403 });
1404 });
1405 });
1406 },
1407
1408 // BIP32 root key can be set by the user
1409 function() {
1410 page.open(url, function(status) {
1411 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1412 // set the root key
1413 page.evaluate(function() {
1414 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1415 });
1416 waitForGenerate(function() {
1417 var actual = page.evaluate(function() {
1418 return $(".address:first").text();
1419 });
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);
1424 fail();
1425 }
1426 next();
1427 });
1428 });
1429 },
1430
1431 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1432 function() {
1433 page.open(url, function(status) {
1434 var expected = "";
1435 // set a mnemonic
1436 page.evaluate(function() {
1437 $(".phrase").val("A non-blank but invalid value");
1438 });
1439 // Accept any confirm dialogs
1440 page.onConfirm = function() {
1441 return true;
1442 };
1443 // set the root key
1444 page.evaluate(function() {
1445 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1446 });
1447 waitForGenerate(function() {
1448 var actual = page.evaluate(function() {
1449 return $(".phrase").val();
1450 });
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);
1455 fail();
1456 }
1457 next();
1458 });
1459 });
1460 },
1461
1462 // Clearing of phrase, passphrase and seed can be cancelled by user
1463 function() {
1464 page.open(url, function(status) {
1465 var expected = "abandon abandon ability";
1466 // set a mnemonic
1467 page.evaluate(function() {
1468 $(".phrase").val("abandon abandon ability");
1469 });
1470 // Cancel any confirm dialogs
1471 page.onConfirm = function() {
1472 return false;
1473 };
1474 // set the root key
1475 page.evaluate(function() {
1476 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1477 });
1478 var actual = page.evaluate(function() {
1479 return $(".phrase").val();
1480 });
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);
1485 fail();
1486 }
1487 next();
1488 });
1489 },
1490
1491 // Custom BIP32 root key is used when changing the derivation path
1492 function() {
1493 page.open(url, function(status) {
1494 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1495 // set the root key
1496 page.evaluate(function() {
1497 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1498 });
1499 waitForGenerate(function() {
1500 // change the derivation path
1501 page.evaluate(function() {
1502 $("#account").val("1").trigger("input");
1503 });
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();
1508 });
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);
1513 fail();
1514 }
1515 next();
1516 });
1517 });
1518 });
1519 },
1520
1521 // Incorrect mnemonic shows error
1522 function() {
1523 page.open(url, function(status) {
1524 // set the root key
1525 page.evaluate(function() {
1526 $(".phrase").val("abandon abandon abandon").trigger("input");
1527 });
1528 waitForFeedback(function() {
1529 // check there is an error shown
1530 var feedback = page.evaluate(function() {
1531 return $(".feedback").text();
1532 });
1533 if (feedback.length <= 0) {
1534 console.log("Invalid mnemonic does not show error");
1535 fail();
1536 }
1537 next();
1538 });
1539 });
1540 },
1541
1542 // Incorrect word shows suggested replacement
1543 function() {
1544 page.open(url, function(status) {
1545 // set the root key
1546 page.evaluate(function() {
1547 $(".phrase").val("abandon abandon abiliti").trigger("input");
1548 });
1549 // check there is a suggestion shown
1550 waitForFeedback(function() {
1551 var feedback = page.evaluate(function() {
1552 return $(".feedback").text();
1553 });
1554 if (feedback.indexOf("did you mean ability?") < 0) {
1555 console.log("Incorrect word does not show suggested replacement");
1556 console.log("Error: " + error);
1557 fail();
1558 }
1559 next();
1560 });
1561 });
1562 },
1563
1564 // Incorrect BIP32 root key shows error
1565 function() {
1566 page.open(url, function(status) {
1567 // set the root key
1568 page.evaluate(function() {
1569 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1570 });
1571 // check there is an error shown
1572 waitForFeedback(function() {
1573 var feedback = page.evaluate(function() {
1574 return $(".feedback").text();
1575 });
1576 if (feedback != "Invalid root key") {
1577 console.log("Invalid root key does not show error");
1578 console.log("Error: " + error);
1579 fail();
1580 }
1581 next();
1582 });
1583 });
1584 },
1585
1586 // Derivation path not starting with m shows error
1587 function() {
1588 page.open(url, function(status) {
1589 // set the mnemonic phrase
1590 page.evaluate(function() {
1591 $(".phrase").val("abandon abandon ability").trigger("input");
1592 });
1593 waitForGenerate(function() {
1594 // select the bip32 tab so custom derivation path can be set
1595 page.evaluate(function() {
1596 $("#bip32-tab a").click();
1597 });
1598 waitForGenerate(function() {
1599 // set the incorrect derivation path
1600 page.evaluate(function() {
1601 $("#bip32 .path").val("n/0").trigger("input");
1602 });
1603 waitForFeedback(function() {
1604 var feedback = page.evaluate(function() {
1605 return $(".feedback").text();
1606 });
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);
1610 fail();
1611 }
1612 next();
1613 });
1614 });
1615 });
1616 });
1617 },
1618
1619 // Derivation path containing invalid characters shows useful error
1620 function() {
1621 page.open(url, function(status) {
1622 // set the mnemonic phrase
1623 page.evaluate(function() {
1624 $(".phrase").val("abandon abandon ability").trigger("input");
1625 });
1626 waitForGenerate(function() {
1627 // select the bip32 tab so custom derivation path can be set
1628 page.evaluate(function() {
1629 $("#bip32-tab a").click();
1630 });
1631 waitForGenerate(function() {
1632 // set the incorrect derivation path
1633 page.evaluate(function() {
1634 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1635 });
1636 waitForFeedback(function() {
1637 var feedback = page.evaluate(function() {
1638 return $(".feedback").text();
1639 });
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);
1643 fail();
1644 }
1645 next();
1646 });
1647 });
1648 });
1649 });
1650 },
1651
1652 // Github Issue 11: Default word length is 15
1653 // https://github.com/iancoleman/bip39/issues/11
1654 function() {
1655 page.open(url, function(status) {
1656 // get the word length
1657 var defaultLength = page.evaluate(function() {
1658 return $(".strength").val();
1659 });
1660 if (defaultLength != 15) {
1661 console.log("Default word length is not 15");
1662 fail();
1663 }
1664 next();
1665 });
1666 },
1667
1668
1669 // Github Issue 12: Generate more rows with private keys hidden
1670 // https://github.com/iancoleman/bip39/issues/12
1671 function() {
1672 page.open(url, function(status) {
1673 // set the phrase
1674 page.evaluate(function() {
1675 $(".phrase").val("abandon abandon ability");
1676 $(".phrase").trigger("input");
1677 });
1678 waitForGenerate(function() {
1679 // toggle private keys hidden, then generate more addresses
1680 page.evaluate(function() {
1681 $(".private-key-toggle").click();
1682 $(".more").click();
1683 });
1684 waitForGenerate(function() {
1685 // check more have been generated
1686 var expected = 40;
1687 var numPrivKeys = page.evaluate(function() {
1688 return $(".privkey").length;
1689 });
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);
1694 fail();
1695 }
1696 // check no private keys are shown
1697 var numHiddenPrivKeys = page.evaluate(function() {
1698 return $(".privkey span[class=invisible]").length;
1699 });
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);
1704 fail();
1705 }
1706 next();
1707 });
1708 });
1709 });
1710 },
1711
1712 // Github Issue 19: Mnemonic is not sensitive to whitespace
1713 // https://github.com/iancoleman/bip39/issues/19
1714 function() {
1715 page.open(url, function(status) {
1716 // set the phrase
1717 var expected = "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1718 page.evaluate(function() {
1719 var doubleSpace = " ";
1720 $(".phrase").val("urge cat" + doubleSpace + "bid");
1721 $(".phrase").trigger("input");
1722 });
1723 waitForGenerate(function() {
1724 // Check the bip32 root key is correct
1725 var actual = page.evaluate(function() {
1726 return $(".root-key").val();
1727 });
1728 if (actual != expected) {
1729 console.log("Mnemonic is sensitive to whitespace");
1730 console.log("Expected: " + expected);
1731 console.log("Actual: " + actual);
1732 fail();
1733 }
1734 next();
1735 });
1736 });
1737 },
1738
1739 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1740 // https://github.com/iancoleman/bip39/issues/23
1741 function() {
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");
1746 });
1747 waitForGenerate(function() {
1748 // 3) select bip32 tab
1749 page.evaluate(function() {
1750 $("#bip32-tab a").click();
1751 });
1752 waitForGenerate(function() {
1753 // 4) switch from bitcoin to litecoin
1754 page.evaluate(function() {
1755 $(".network").val("2").trigger("change");
1756 });
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();
1762 });
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);
1767 fail();
1768 }
1769 // 5) Check address is displayed correctly
1770 var expected = "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1771 var actual = page.evaluate(function() {
1772 return $(".address:first").text();
1773 });
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);
1778 fail();
1779 }
1780 next();
1781 });
1782 });
1783 });
1784 });
1785 },
1786
1787 // Github Issue 23 Part 2: Coin selection in derivation path
1788 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1789 function() {
1790 page.open(url, function(status) {
1791 // set the phrase
1792 page.evaluate(function() {
1793 $(".phrase").val("abandon abandon ability").trigger("input");
1794 });
1795 waitForGenerate(function() {
1796 // switch from bitcoin to clam
1797 page.evaluate(function() {
1798 $(".network").val("9").trigger("change");
1799 });
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();
1805 });
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);
1810 fail();
1811 }
1812 next();
1813 });
1814 });
1815 });
1816 },
1817
1818 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1819 // https://github.com/iancoleman/bip39/issues/26
1820 function() {
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");
1825 });
1826 waitForGenerate(function() {
1827 // 4) switch from bitcoin to viacoin
1828 page.evaluate(function() {
1829 $(".network").val("6").trigger("change");
1830 });
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();
1836 });
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);
1841 fail();
1842 }
1843 next();
1844 });
1845 });
1846 });
1847 },
1848
1849 // Selecting a language with no existing phrase should generate a phrase in
1850 // that language.
1851 function() {
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");
1861 });
1862 waitForGenerate(function() {
1863 // Check the mnemonic is in Japanese
1864 var phrase = page.evaluate(function() {
1865 return $(".phrase").val();
1866 });
1867 if (phrase.length <= 0) {
1868 console.log("No Japanese phrase generated");
1869 fail();
1870 }
1871 if (phrase.charCodeAt(0) < 128) {
1872 console.log("First character of Japanese phrase is ascii");
1873 console.log("Phrase: " + phrase);
1874 fail();
1875 }
1876 next();
1877 });
1878 });
1879 },
1880
1881 // Selecting a language with existing phrase should update the phrase to use
1882 // that language.
1883 function() {
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");
1888 });
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");
1898 });
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();
1904 });
1905 if (actual != expected) {
1906 console.log("Changing language with existing phrase");
1907 console.log("Expected: " + expected);
1908 console.log("Actual: " + actual);
1909 fail();
1910 }
1911 // Check the address is correct
1912 var expected = "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1913 var actual = page.evaluate(function() {
1914 return $(".address:first").text();
1915 });
1916 if (actual != expected) {
1917 console.log("Changing language generates incorrect address");
1918 console.log("Expected: " + expected);
1919 console.log("Actual: " + actual);
1920 fail();
1921 }
1922 next();
1923 });
1924 });
1925 });
1926 },
1927
1928 // Suggested replacement for erroneous word in non-English language
1929 function() {
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");
1934 });
1935 waitForFeedback(function() {
1936 // Check the suggestion is correct
1937 var feedback = page.evaluate(function() {
1938 return $(".feedback").text();
1939 });
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);
1943 fail();
1944 }
1945 next();
1946 });
1947 });
1948 },
1949
1950
1951 // Japanese word does not break across lines.
1952 // Point 2 from
1953 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1954 function() {
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; }'");
1960 fail();
1961 }
1962 // Run the next test
1963 next();
1964 });
1965 },
1966
1967 // Language can be specified at page load using hash value in url
1968 function() {
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";
1973 });
1974 // Generate a random phrase
1975 page.evaluate(function() {
1976 $(".generate").trigger("click");
1977 });
1978 waitForGenerate(function() {
1979 // Check the phrase is in Japanese
1980 var phrase = page.evaluate(function() {
1981 return $(".phrase").val();
1982 });
1983 if (phrase.length <= 0) {
1984 console.log("No phrase generated using url hash");
1985 fail();
1986 }
1987 if (phrase.charCodeAt(0) < 128) {
1988 console.log("Language not detected from url hash on page load.");
1989 console.log("Phrase: " + phrase);
1990 fail();
1991 }
1992 next();
1993 });
1994 });
1995 },
1996
1997 // Entropy unit tests
1998 function() {
1999 page.open(url, function(status) {
2000 var response = page.evaluate(function() {
2001 var e;
2002 // binary entropy is detected
2003 try {
2004 e = Entropy.fromString("01010101");
2005 if (e.base.str != "binary") {
2006 return "Binary entropy not detected correctly";
2007 }
2008 }
2009 catch (e) {
2010 return e.message;
2011 }
2012 // base6 entropy is detected
2013 try {
2014 e = Entropy.fromString("012345012345");
2015 if (e.base.str != "base 6") {
2016 return "base6 entropy not detected correctly";
2017 }
2018 }
2019 catch (e) {
2020 return e.message;
2021 }
2022 // dice entropy is detected
2023 try {
2024 e = Entropy.fromString("123456123456");
2025 if (e.base.str != "base 6 (dice)") {
2026 return "dice entropy not detected correctly";
2027 }
2028 }
2029 catch (e) {
2030 return e.message;
2031 }
2032 // base10 entropy is detected
2033 try {
2034 e = Entropy.fromString("0123456789");
2035 if (e.base.str != "base 10") {
2036 return "base10 entropy not detected correctly";
2037 }
2038 }
2039 catch (e) {
2040 return e.message;
2041 }
2042 // hex entropy is detected
2043 try {
2044 e = Entropy.fromString("0123456789ABCDEF");
2045 if (e.base.str != "hexadecimal") {
2046 return "hexadecimal entropy not detected correctly";
2047 }
2048 }
2049 catch (e) {
2050 return e.message;
2051 }
2052 // card entropy is detected
2053 try {
2054 e = Entropy.fromString("AC4DTHKS");
2055 if (e.base.str != "card") {
2056 return "card entropy not detected correctly";
2057 }
2058 }
2059 catch (e) {
2060 return e.message;
2061 }
2062 // entropy is case insensitive
2063 try {
2064 e = Entropy.fromString("aBcDeF");
2065 if (e.cleanStr != "aBcDeF") {
2066 return "Entropy should not be case sensitive";
2067 }
2068 }
2069 catch (e) {
2070 return e.message;
2071 }
2072 // dice entropy is converted to base6
2073 try {
2074 e = Entropy.fromString("123456");
2075 if (e.cleanStr != "123450") {
2076 return "Dice entropy is not automatically converted to base6";
2077 }
2078 }
2079 catch (e) {
2080 return e.message;
2081 }
2082 // dice entropy is preferred to base6 if ambiguous
2083 try {
2084 e = Entropy.fromString("12345");
2085 if (e.base.str != "base 6 (dice)") {
2086 return "dice not used as default over base 6";
2087 }
2088 }
2089 catch (e) {
2090 return e.message;
2091 }
2092 // unused characters are ignored
2093 try {
2094 e = Entropy.fromString("fghijkl");
2095 if (e.cleanStr != "f") {
2096 return "additional characters are not ignored";
2097 }
2098 }
2099 catch (e) {
2100 return e.message;
2101 }
2102 // the lowest base is used by default
2103 // 7 could be decimal or hexadecimal, but should be detected as decimal
2104 try {
2105 e = Entropy.fromString("7");
2106 if (e.base.str != "base 10") {
2107 return "lowest base is not used";
2108 }
2109 }
2110 catch (e) {
2111 return e.message;
2112 }
2113 // Leading zeros are retained
2114 try {
2115 e = Entropy.fromString("000A");
2116 if (e.cleanStr != "000A") {
2117 return "Leading zeros are not retained";
2118 }
2119 }
2120 catch (e) {
2121 return e.message;
2122 }
2123 // Leading zeros are correctly preserved for hex in binary string
2124 try {
2125 e = Entropy.fromString("2A");
2126 if (e.binaryStr != "00101010") {
2127 return "Hex leading zeros are not correct in binary";
2128 }
2129 }
2130 catch (e) {
2131 return e.message;
2132 }
2133 // Leading zeros are not used for base 6 as binary string
2134 try {
2135 e = Entropy.fromString("2");
2136 if (e.binaryStr != "10") {
2137 return "Base 6 as binary has leading zeros";
2138 }
2139 }
2140 catch (e) {
2141 return e.message;
2142 }
2143 // Leading zeros are not used for base 10 as binary string
2144 try {
2145 e = Entropy.fromString("7");
2146 if (e.binaryStr != "111") {
2147 return "Base 10 as binary has leading zeros";
2148 }
2149 }
2150 catch (e) {
2151 return e.message;
2152 }
2153 // Leading zeros are not used for card entropy as binary string
2154 try {
2155 e = Entropy.fromString("2c");
2156 if (e.binaryStr != "1") {
2157 return "Card entropy as binary has leading zeros";
2158 }
2159 }
2160 catch (e) {
2161 return e.message;
2162 }
2163 // Keyboard mashing results in weak entropy
2164 // Despite being a long string, it's less than 30 bits of entropy
2165 try {
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";
2169 }
2170 }
2171 catch (e) {
2172 return e.message;
2173 }
2174 // Card entropy is used if every pair could be a card
2175 try {
2176 e = Entropy.fromString("4c3c2c");
2177 if (e.base.str != "card") {
2178 return "Card entropy not used if all pairs are cards";
2179 }
2180 }
2181 catch (e) {
2182 return e.message;
2183 }
2184 // Card entropy uses base 52
2185 // [ cards, binary ]
2186 try {
2187 var cards = [
2188 [ "ac", "00000" ],
2189 [ "acac", "00000000000" ],
2190 [ "acac2c", "000000000001" ],
2191 [ "acks", "00000110011" ],
2192 [ "acacks", "00000000000110011" ],
2193 [ "2c", "1" ],
2194 [ "3d", "1111" ],
2195 [ "4h", "11101" ],
2196 [ "5s", "101011" ],
2197 [ "6c", "101" ],
2198 [ "7d", "10011" ],
2199 [ "8h", "100001" ],
2200 [ "9s", "101111" ],
2201 [ "tc", "1001" ],
2202 [ "jd", "10111" ],
2203 [ "qh", "100101" ],
2204 [ "ks", "110011" ],
2205 [ "ks2c", "101001011101" ],
2206 [ "KS2C", "101001011101" ],
2207 ];
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;
2215 }
2216 }
2217 }
2218 catch (e) {
2219 return e.message;
2220 }
2221 return "PASS";
2222 });
2223 if (response != "PASS") {
2224 console.log("Entropy unit tests");
2225 console.log(response);
2226 fail();
2227 };
2228 next();
2229 });
2230 },
2231
2232 // Entropy can be entered by the user
2233 function() {
2234 page.open(url, function(status) {
2235 expected = {
2236 mnemonic: "abandon abandon ability",
2237 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2238 }
2239 // use entropy
2240 page.evaluate(function() {
2241 $(".use-entropy").prop("checked", true).trigger("change");
2242 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2243 });
2244 // check the mnemonic is set and address is correct
2245 waitForGenerate(function() {
2246 var actual = page.evaluate(function() {
2247 return {
2248 address: $(".address:first").text(),
2249 mnemonic: $(".phrase").val(),
2250 }
2251 });
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);
2256 fail();
2257 }
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);
2262 fail();
2263 }
2264 next();
2265 });
2266 });
2267 },
2268
2269 // A warning about entropy is shown to the user, with additional information
2270 function() {
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) {
2277 return false;
2278 }
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) {
2282 return false;
2283 }
2284 return true;
2285 });
2286 // check the warnings and information are shown
2287 if (!hasWarning) {
2288 console.log("Page does not contain warning about using own entropy");
2289 fail();
2290 }
2291 next();
2292 });
2293 },
2294
2295 // The types of entropy available are described to the user
2296 function() {
2297 page.open(url, function(status) {
2298 // get placeholder text for entropy field
2299 var placeholder = page.evaluate(function() {
2300 return $(".entropy").attr("placeholder");
2301 });
2302 var options = [
2303 "binary",
2304 "base 6",
2305 "dice",
2306 "base 10",
2307 "hexadecimal",
2308 ];
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);
2313 fail();
2314 }
2315 }
2316 next();
2317 });
2318 },
2319
2320 // The actual entropy used is shown to the user
2321 function() {
2322 page.open(url, function(status) {
2323 // use entropy
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");
2328 });
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();
2334 });
2335 if (entropyText.indexOf(expectedText) == -1) {
2336 console.log("Actual entropy used is not shown");
2337 fail();
2338 }
2339 next();
2340 });
2341 });
2342 },
2343
2344 // Binary entropy can be entered
2345 function() {
2346 page.open(url, function(status) {
2347 // use entropy
2348 page.evaluate(function() {
2349 $(".use-entropy").prop("checked", true).trigger("change");
2350 $(".entropy").val("01").trigger("input");
2351 });
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();
2356 });
2357 if (entropyText.indexOf("binary") == -1) {
2358 console.log("Binary entropy is not detected and presented to user");
2359 fail();
2360 }
2361 next();
2362 });
2363 });
2364 },
2365
2366 // Base 6 entropy can be entered
2367 function() {
2368 page.open(url, function(status) {
2369 // use entropy
2370 page.evaluate(function() {
2371 $(".use-entropy").prop("checked", true).trigger("change");
2372 $(".entropy").val("012345").trigger("input");
2373 });
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();
2378 });
2379 if (entropyText.indexOf("base 6") == -1) {
2380 console.log("Base 6 entropy is not detected and presented to user");
2381 fail();
2382 }
2383 next();
2384 });
2385 });
2386 },
2387
2388 // Base 6 dice entropy can be entered
2389 function() {
2390 page.open(url, function(status) {
2391 // use entropy
2392 page.evaluate(function() {
2393 $(".use-entropy").prop("checked", true).trigger("change");
2394 $(".entropy").val("123456").trigger("input");
2395 });
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();
2400 });
2401 if (entropyText.indexOf("dice") == -1) {
2402 console.log("Dice entropy is not detected and presented to user");
2403 fail();
2404 }
2405 next();
2406 });
2407 });
2408 },
2409
2410 // Base 10 entropy can be entered
2411 function() {
2412 page.open(url, function(status) {
2413 // use entropy
2414 page.evaluate(function() {
2415 $(".use-entropy").prop("checked", true).trigger("change");
2416 $(".entropy").val("789").trigger("input");
2417 });
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();
2422 });
2423 if (entropyText.indexOf("base 10") == -1) {
2424 console.log("Base 10 entropy is not detected and presented to user");
2425 fail();
2426 }
2427 next();
2428 });
2429 });
2430 },
2431
2432 // Hexadecimal entropy can be entered
2433 function() {
2434 page.open(url, function(status) {
2435 // use entropy
2436 page.evaluate(function() {
2437 $(".use-entropy").prop("checked", true).trigger("change");
2438 $(".entropy").val("abcdef").trigger("input");
2439 });
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();
2444 });
2445 if (entropyText.indexOf("hexadecimal") == -1) {
2446 console.log("Hexadecimal entropy is not detected and presented to user");
2447 fail();
2448 }
2449 next();
2450 });
2451 });
2452 },
2453
2454 // Dice entropy value is shown as the converted base 6 value
2455 function() {
2456 page.open(url, function(status) {
2457 // use entropy
2458 page.evaluate(function() {
2459 $(".use-entropy").prop("checked", true).trigger("change");
2460 $(".entropy").val("123456").trigger("input");
2461 });
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();
2466 });
2467 if (entropyText.indexOf("123450") == -1) {
2468 console.log("Dice entropy is not shown to user as base 6 value");
2469 fail();
2470 }
2471 if (entropyText.indexOf("123456") > -1) {
2472 console.log("Dice entropy value is shown instead of true base 6 value");
2473 fail();
2474 }
2475 next();
2476 });
2477 });
2478 },
2479
2480 // The number of bits of entropy accumulated is shown
2481 function() {
2482 page.open(url, function(status) {
2483 //[ entropy, bits ]
2484 var tests = [
2485 [ "0000 0000 0000 0000 0000", "20" ],
2486 [ "0", "1" ],
2487 [ "0000", "4" ],
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
2490 [ "8", "4" ],
2491 [ "F", "4" ],
2492 [ "29", "5" ],
2493 [ "0A", "8" ],
2494 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2495 [ "2A", "8" ],
2496 [ "4A", "8" ],
2497 [ "8A", "8" ],
2498 [ "FA", "8" ],
2499 [ "000A", "16" ],
2500 [ "5555", "11" ],
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)
2502 [ "2227", "12" ],
2503 [ "222F", "16" ],
2504 [ "FFFF", "16" ],
2505 ]
2506 // use entropy
2507 page.evaluate(function(e) {
2508 $(".use-entropy").prop("checked", true).trigger("change");
2509 });
2510 // Run each test
2511 var nextTest = function runNextTest(i) {
2512 var entropy = tests[i][0];
2513 var expected = tests[i][1];
2514 // set entropy
2515 page.evaluate(function(e) {
2516 $(".entropy").val(e).trigger("input");
2517 }, entropy);
2518 // check the number of bits of entropy is shown
2519 waitForEntropyFeedback(function() {
2520 var entropyText = page.evaluate(function() {
2521 return $(".entropy-error").text();
2522 });
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);
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 // The number of bits of entropy to reach the next mnemonic strength is shown
2542 function() {
2543 page.open(url, function(status) {
2544 // use entropy
2545 page.evaluate(function() {
2546 $(".use-entropy").prop("checked", true).trigger("change");
2547 $(".entropy").val("7654321").trigger("input");
2548 });
2549 // check the amount of additional entropy required is shown
2550 waitForEntropyFeedback(function() {
2551 var entropyText = page.evaluate(function() {
2552 return $(".entropy-container").text();
2553 });
2554 if (entropyText.indexOf("3 more base 10 chars required") == -1) {
2555 console.log("Additional entropy requirement is not shown");
2556 fail();
2557 }
2558 next();
2559 });
2560 });
2561 },
2562
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
2569 function() {
2570 page.open(url, function(status) {
2571 var tests = [
2572 {
2573 entropy: "A",
2574 words: 0,
2575 nextStrength: "an extremely weak",
2576 },
2577 {
2578 entropy: "AAAAAAAA",
2579 words: 3,
2580 nextStrength: "a very weak",
2581 },
2582 {
2583 entropy: "AAAAAAAA B",
2584 words: 3,
2585 nextStrength: "a very weak",
2586 },
2587 {
2588 entropy: "AAAAAAAA BBBBBBBB",
2589 words: 6,
2590 nextStrength: "a weak",
2591 },
2592 {
2593 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2594 words: 9,
2595 nextStrength: "a strong",
2596 },
2597 {
2598 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2599 words: 12,
2600 nextStrength: "a very strong",
2601 },
2602 {
2603 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE",
2604 words: 15,
2605 nextStrength: "an extremely strong",
2606 }
2607 ];
2608 // use entropy
2609 page.evaluate(function() {
2610 $(".use-entropy").prop("checked", true).trigger("change");
2611 });
2612 var nextTest = function runNextTest(i) {
2613 test = tests[i];
2614 page.evaluate(function(e) {
2615 $(".addresses").empty();
2616 $(".phrase").val("");
2617 $(".entropy").val(e).trigger("input");
2618 }, test.entropy);
2619 if (test.words == 0) {
2620 var mnemonic = page.evaluate(function() {
2621 return $(".phrase").val();
2622 });
2623 if (mnemonic.length > 0) {
2624 console.log("Mnemonic length for " + test.nextStrength + " strength is not " + test.words);
2625 console.log("Mnemonic: " + mnemonic);
2626 fail();
2627 }
2628 var isLastTest = i == tests.length - 1;
2629 if (isLastTest) {
2630 next();
2631 }
2632 else {
2633 runNextTest(i+1);
2634 }
2635 }
2636 else {
2637 waitForGenerate(function() {
2638 // check the strength of the current mnemonic
2639 var mnemonic = page.evaluate(function() {
2640 return $(".phrase").val();
2641 });
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);
2645 fail();
2646 }
2647 // check the strength of the next mnemonic is shown
2648 var entropyText = page.evaluate(function() {
2649 return $(".entropy-container").text();
2650 });
2651 if (entropyText.indexOf("required to generate " + test.nextStrength + " mnemonic") == -1) {
2652 console.log("Strength indicator for " + test.nextStrength + " mnemonic is incorrect");
2653 fail();
2654 }
2655 var isLastTest = i == tests.length - 1;
2656 if (isLastTest) {
2657 next();
2658 }
2659 else {
2660 runNextTest(i+1);
2661 }
2662 });
2663 }
2664 }
2665 nextTest(0);
2666 });
2667 },
2668
2669 // Entropy is truncated from the right
2670 function() {
2671 page.open(url, function(status) {
2672 var expected = "abandon abandon ability";
2673 // use entropy
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");
2679 });
2680 // check the entropy is truncated from the right
2681 waitForGenerate(function() {
2682 var actual = page.evaluate(function() {
2683 return $(".phrase").val();
2684 });
2685 if (actual != expected) {
2686 console.log("Entropy is not truncated from the right");
2687 console.log("Expected: " + expected);
2688 console.log("Got: " + actual);
2689 fail();
2690 }
2691 next();
2692 });
2693 });
2694 },
2695
2696 // Very large entropy results in very long mnemonics
2697 function() {
2698 page.open(url, function(status) {
2699 // use entropy
2700 page.evaluate(function() {
2701 $(".use-entropy").prop("checked", true).trigger("change");
2702 var entropy = "";
2703 // Generate a very long entropy string
2704 for (var i=0; i<33; i++) {
2705 entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2706 }
2707 $(".entropy").val(entropy).trigger("input");
2708 });
2709 // check the mnemonic is very long
2710 waitForGenerate(function() {
2711 var wordCount = page.evaluate(function() {
2712 return $(".phrase").val().split(" ").length;
2713 });
2714 if (wordCount != 99) {
2715 console.log("Large entropy does not generate long mnemonic");
2716 console.log("Expected 99 words, got " + wordCount);
2717 fail();
2718 }
2719 next();
2720 });
2721 });
2722 },
2723
2724 // Is compatible with bip32jp entropy
2725 // https://bip32jp.github.io/english/index.html
2726 // NOTES:
2727 // Is incompatible with:
2728 // base 6 with leading zeros
2729 // base 6 wth 12 words / 53 chars
2730 // base 20
2731 function() {
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";
2734 // use entropy
2735 page.evaluate(function() {
2736 $(".use-entropy").prop("checked", true).trigger("change");
2737 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2738 $(".entropy").val(entropy).trigger("input");
2739 });
2740 // check the mnemonic matches the expected value from bip32jp
2741 waitForGenerate(function() {
2742 var actual = page.evaluate(function() {
2743 return $(".phrase").val();
2744 });
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);
2749 fail();
2750 }
2751 next();
2752 });
2753 });
2754 },
2755
2756 // Blank entropy does not generate mnemonic or addresses
2757 function() {
2758 page.open(url, function(status) {
2759 // use entropy
2760 page.evaluate(function() {
2761 $(".use-entropy").prop("checked", true).trigger("change");
2762 $(".entropy").val("").trigger("input");
2763 });
2764 waitForFeedback(function() {
2765 // check there is no mnemonic
2766 var phrase = page.evaluate(function() {
2767 return $(".phrase").val();
2768 });
2769 if (phrase != "") {
2770 console.log("Blank entropy does not result in blank mnemonic");
2771 console.log("Got: " + phrase);
2772 fail();
2773 }
2774 // check there are no addresses displayed
2775 var addresses = page.evaluate(function() {
2776 return $(".address").length;
2777 });
2778 if (addresses != 0) {
2779 console.log("Blank entropy does not result in zero addresses");
2780 fail();
2781 }
2782 // Check the feedback says 'blank entropy'
2783 var feedback = page.evaluate(function() {
2784 return $(".feedback").text();
2785 });
2786 if (feedback != "Blank entropy") {
2787 console.log("Blank entropy does not show feedback message");
2788 fail();
2789 }
2790 next();
2791 });
2792 });
2793 },
2794
2795 // If you wish to add more tests, do so here...
2796
2797 // Here is a blank test template
2798 /*
2799
2800 function() {
2801 page.open(url, function(status) {
2802 // Do something on the page
2803 page.evaluate(function() {
2804 $(".phrase").val("abandon abandon ability").trigger("input");
2805 });
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();
2811 });
2812 if (actual != expected) {
2813 console.log("A specific message about what failed");
2814 console.log("Expected: " + expected);
2815 console.log("Actual: " + actual);
2816 fail();
2817 }
2818 // Run the next test
2819 next();
2820 });
2821 });
2822 },
2823
2824 */
2825
2826 ];
2827
2828 console.log("Running tests...");
2829 tests = shuffle(tests);
2830 next();