]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
merging
[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 = 20000;
8
9 page.viewportSize = {
10 width: 1024,
11 height: 720
12 };
13
14 page.onResourceError = function(e) {
15 console.log("Error loading " + e.url);
16 phantom.exit();
17 }
18
19 function fail() {
20 console.log("Failed");
21 phantom.exit();
22 }
23
24 function waitForGenerate(fn, maxTime) {
25 if (!maxTime) {
26 maxTime = testMaxTime;
27 }
28 var start = new Date().getTime();
29 var prevAddressCount = -1;
30 var wait = function keepWaiting() {
31 var now = new Date().getTime();
32 var hasTimedOut = now - start > maxTime;
33 var addressCount = page.evaluate(function() {
34 return $(".address").length;
35 });
36 var hasFinished = addressCount > 0 && addressCount == prevAddressCount;
37 prevAddressCount = addressCount;
38 if (hasFinished) {
39 fn();
40 }
41 else if (hasTimedOut) {
42 console.log("Test timed out");
43 fn();
44 }
45 else {
46 setTimeout(keepWaiting, 100);
47 }
48 }
49 wait();
50 }
51
52 function waitForFeedback(fn, maxTime) {
53 if (!maxTime) {
54 maxTime = testMaxTime;
55 }
56 var start = new Date().getTime();
57 var wait = function keepWaiting() {
58 var now = new Date().getTime();
59 var hasTimedOut = now - start > maxTime;
60 if (hasTimedOut) {
61 console.log("Test timed out");
62 fn();
63 return;
64 }
65 var feedback = page.evaluate(function() {
66 var feedback = $(".feedback");
67 if (feedback.css("display") == "none") {
68 return "";
69 }
70 return feedback.text();
71 });
72 var hasFinished = feedback.length > 0 && feedback != "Calculating...";
73 if (hasFinished) {
74 fn();
75 }
76 else {
77 setTimeout(keepWaiting, 100);
78 }
79 }
80 wait();
81 }
82
83 function waitForEntropyFeedback(fn, maxTime) {
84 if (!maxTime) {
85 maxTime = testMaxTime;
86 }
87 var origFeedback = page.evaluate(function() {
88 return $(".entropy-container").text();
89 });
90 var start = new Date().getTime();
91 var wait = function keepWaiting() {
92 var now = new Date().getTime();
93 var hasTimedOut = now - start > maxTime;
94 if (hasTimedOut) {
95 console.log("Test timed out");
96 fn();
97 return;
98 }
99 var feedback = page.evaluate(function() {
100 return $(".entropy-container").text();
101 });
102 var hasFinished = feedback != origFeedback;
103 if (hasFinished) {
104 fn();
105 }
106 else {
107 setTimeout(keepWaiting, 100);
108 }
109 }
110 wait();
111 }
112
113 function next() {
114 if (tests.length > 0) {
115 var testsStr = tests.length == 1 ? "test" : "tests";
116 console.log(tests.length + " " + testsStr + " remaining");
117 tests.shift()();
118 }
119 else {
120 console.log("Finished with 0 failures");
121 phantom.exit();
122 }
123 }
124
125 /**
126 * Randomize array element order in-place.
127 * Using Durstenfeld shuffle algorithm.
128 * See http://stackoverflow.com/a/12646864
129 */
130 function shuffle(array) {
131 for (var i = array.length - 1; i > 0; i--) {
132 var j = Math.floor(Math.random() * (i + 1));
133 var temp = array[i];
134 array[i] = array[j];
135 array[j] = temp;
136 }
137 return array;
138 }
139
140 tests = [
141
142 // Page loads with status of 'success'
143 function() {
144 page.open(url, function(status) {
145 if (status != "success") {
146 console.log("Page did not load with status 'success'");
147 fail();
148 }
149 next();
150 });
151 },
152
153 // Page has text
154 function() {
155 page.open(url, function(status) {
156 var content = page.evaluate(function() {
157 return document.body.textContent.trim();
158 });
159 if (!content) {
160 console.log("Page does not have text");
161 fail();
162 }
163 next();
164 });
165 },
166
167 // Entering mnemonic generates addresses
168 function() {
169 page.open(url, function(status) {
170 // set the phrase
171 page.evaluate(function() {
172 $(".phrase").val("abandon abandon ability").trigger("input");
173 });
174 // get the address
175 waitForGenerate(function() {
176 var addressCount = page.evaluate(function() {
177 return $(".address").length;
178 });
179 if (addressCount != 20) {
180 console.log("Mnemonic did not generate addresses");
181 console.log("Expected: " + expected);
182 console.log("Got: " + actual);
183 fail();
184 }
185 next();
186 });
187 });
188 },
189
190 // Random button generates random mnemonic
191 function() {
192 page.open(url, function(status) {
193 // check initial phrase is empty
194 var phrase = page.evaluate(function() {
195 return $(".phrase").text();
196 });
197 if (phrase != "") {
198 console.log("Initial phrase is not blank");
199 fail();
200 }
201 // press the 'generate' button
202 page.evaluate(function() {
203 $(".generate").click();
204 });
205 // get the new phrase
206 waitForGenerate(function() {
207 var phrase = page.evaluate(function() {
208 return $(".phrase").val();
209 });
210 if (phrase.length <= 0) {
211 console.log("Phrase not generated by pressing button");
212 fail();
213 }
214 next();
215 });
216 });
217 },
218
219 // Mnemonic length can be customized
220 function() {
221 page.open(url, function(status) {
222 // set the length to 6
223 var expectedLength = "6";
224 page.evaluate(function() {
225 $(".strength option[selected]").removeAttr("selected");
226 $(".strength option[value=6]").prop("selected", true);
227 });
228 // press the 'generate' button
229 page.evaluate(function() {
230 $(".generate").click();
231 });
232 // check the new phrase is six words long
233 waitForGenerate(function() {
234 var actualLength = page.evaluate(function() {
235 var words = $(".phrase").val().split(" ");
236 return words.length;
237 });
238 if (actualLength != expectedLength) {
239 console.log("Phrase not generated with correct length");
240 console.log("Expected: " + expectedLength);
241 console.log("Actual: " + actualLength);
242 fail();
243 }
244 next();
245 });
246 });
247 },
248
249 // Passphrase can be set
250 function() {
251 page.open(url, function(status) {
252 // set the phrase and passphrase
253 var expected = "15pJzUWPGzR7avffV9nY5by4PSgSKG9rba";
254 page.evaluate(function() {
255 $(".phrase").val("abandon abandon ability");
256 $(".passphrase").val("secure_passphrase").trigger("input");
257 });
258 // check the address is generated correctly
259 waitForGenerate(function() {
260 var actual = page.evaluate(function() {
261 return $(".address:first").text();
262 });
263 if (actual != expected) {
264 console.log("Passphrase results in wrong address");
265 console.log("Expected: " + expected);
266 console.log("Actual: " + actual);
267 fail();
268 }
269 next();
270 });
271 });
272 },
273
274 // Network can be set to bitcoin testnet
275 function() {
276 page.open(url, function(status) {
277 // set the phrase and coin
278 var expected = "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi";
279 page.evaluate(function() {
280 $(".phrase").val("abandon abandon ability");
281 $(".phrase").trigger("input");
282 $(".network option[selected]").removeAttr("selected");
283 $(".network option").filter(function() {
284 return $(this).html() == "Bitcoin Testnet";
285 }).prop("selected", true);
286 $(".network").trigger("change");
287 });
288 // check the address is generated correctly
289 waitForGenerate(function() {
290 var actual = page.evaluate(function() {
291 return $(".address:first").text();
292 });
293 if (actual != expected) {
294 console.log("Bitcoin testnet address is incorrect");
295 console.log("Expected: " + expected);
296 console.log("Actual: " + actual);
297 fail();
298 }
299 next();
300 });
301 });
302 },
303
304 // Network can be set to litecoin
305 function() {
306 page.open(url, function(status) {
307 // set the phrase and coin
308 var expected = "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
309 page.evaluate(function() {
310 $(".phrase").val("abandon abandon ability");
311 $(".phrase").trigger("input");
312 $(".network option[selected]").removeAttr("selected");
313 $(".network option").filter(function() {
314 return $(this).html() == "Litecoin";
315 }).prop("selected", true);
316 $(".network").trigger("change");
317 });
318 // check the address is generated correctly
319 waitForGenerate(function() {
320 var actual = page.evaluate(function() {
321 return $(".address:first").text();
322 });
323 if (actual != expected) {
324 console.log("Litecoin address is incorrect");
325 console.log("Expected: " + expected);
326 console.log("Actual: " + actual);
327 fail();
328 }
329 next();
330 });
331 });
332 },
333
334 // Network can be set to ripple
335 function() {
336 page.open(url, function(status) {
337 // set the phrase and coin
338 var expected = "rLTFnqbmCVPGx6VfaygdtuKWJgcN4v1zRS";
339 page.evaluate(function() {
340 $(".phrase").val("ill clump only blind unit burden thing track silver cloth review awake useful craft whale all satisfy else trophy sunset walk vanish hope valve");
341 $(".phrase").trigger("input");
342 $(".network option[selected]").removeAttr("selected");
343 $(".network option").filter(function() {
344 return $(this).html() == "Ripple";
345 }).prop("selected", true);
346 $(".network").trigger("change");
347 });
348 // check the address is generated correctly
349 waitForGenerate(function() {
350 var actual = page.evaluate(function() {
351 return $(".address:first").text();
352 });
353 if (actual != expected) {
354 console.log("Litecoin address is incorrect");
355 console.log("Expected: " + expected);
356 console.log("Actual: " + actual);
357 fail();
358 }
359 next();
360 });
361 });
362 },
363
364 // Network can be set to dogecoin
365 function() {
366 page.open(url, function(status) {
367 // set the phrase and coin
368 var expected = "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
369 page.evaluate(function() {
370 $(".phrase").val("abandon abandon ability");
371 $(".phrase").trigger("input");
372 $(".network option[selected]").removeAttr("selected");
373 $(".network option").filter(function() {
374 return $(this).html() == "Dogecoin";
375 }).prop("selected", true);
376 $(".network").trigger("change");
377 });
378 // check the address is generated correctly
379 waitForGenerate(function() {
380 var actual = page.evaluate(function() {
381 return $(".address:first").text();
382 });
383 if (actual != expected) {
384 console.log("Dogecoin address is incorrect");
385 console.log("Expected: " + expected);
386 console.log("Actual: " + actual);
387 fail();
388 }
389 next();
390 });
391 });
392 },
393
394 // Network can be set to shadowcash
395 function() {
396 page.open(url, function(status) {
397 // set the phrase and coin
398 var expected = "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
399 page.evaluate(function() {
400 $(".phrase").val("abandon abandon ability");
401 $(".phrase").trigger("input");
402 $(".network option[selected]").removeAttr("selected");
403 $(".network option").filter(function() {
404 return $(this).html() == "ShadowCash";
405 }).prop("selected", true);
406 $(".network").trigger("change");
407 });
408 // check the address is generated correctly
409 waitForGenerate(function() {
410 var actual = page.evaluate(function() {
411 return $(".address:first").text();
412 });
413 if (actual != expected) {
414 console.log("Shadowcash address is incorrect");
415 console.log("Expected: " + expected);
416 console.log("Actual: " + actual);
417 fail();
418 }
419 next();
420 });
421 });
422 },
423
424 // Network can be set to shadowcash testnet
425 function() {
426 page.open(url, function(status) {
427 // set the phrase and coin
428 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
429 page.evaluate(function() {
430 $(".phrase").val("abandon abandon ability");
431 $(".phrase").trigger("input");
432 $(".network option[selected]").removeAttr("selected");
433 $(".network option").filter(function() {
434 return $(this).html() == "ShadowCash Testnet";
435 }).prop("selected", true);
436 $(".network").trigger("change");
437 });
438 // check the address is generated correctly
439 waitForGenerate(function() {
440 var actual = page.evaluate(function() {
441 return $(".address:first").text();
442 });
443 if (actual != expected) {
444 console.log("Shadowcash testnet address is incorrect");
445 console.log("Expected: " + expected);
446 console.log("Actual: " + actual);
447 fail();
448 }
449 next();
450 });
451 });
452 },
453
454 // Network can be set to viacoin
455 function() {
456 page.open(url, function(status) {
457 // set the phrase and coin
458 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
459 page.evaluate(function() {
460 $(".phrase").val("abandon abandon ability");
461 $(".phrase").trigger("input");
462 $(".network option[selected]").removeAttr("selected");
463 $(".network option").filter(function() {
464 return $(this).html() == "Viacoin";
465 }).prop("selected", true);
466 $(".network").trigger("change");
467 });
468 // check the address is generated correctly
469 waitForGenerate(function() {
470 var actual = page.evaluate(function() {
471 return $(".address:first").text();
472 });
473 if (actual != expected) {
474 console.log("Viacoin address is incorrect");
475 console.log("Expected: " + expected);
476 console.log("Actual: " + actual);
477 fail();
478 }
479 next();
480 });
481 });
482 },
483
484 // Network can be set to viacoin testnet
485 function() {
486 page.open(url, function(status) {
487 // set the phrase and coin
488 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
489 page.evaluate(function() {
490 $(".phrase").val("abandon abandon ability");
491 $(".phrase").trigger("input");
492 $(".network option[selected]").removeAttr("selected");
493 $(".network option").filter(function() {
494 return $(this).html() == "Viacoin Testnet";
495 }).prop("selected", true);
496 $(".network").trigger("change");
497 });
498 // check the address is generated correctly
499 waitForGenerate(function() {
500 var actual = page.evaluate(function() {
501 return $(".address:first").text();
502 });
503 if (actual != expected) {
504 console.log("Viacoin testnet address is incorrect");
505 console.log("Expected: " + expected);
506 console.log("Actual: " + actual);
507 fail();
508 }
509 next();
510 });
511 });
512 },
513
514 // Network can be set to jumbucks
515 function() {
516 page.open(url, function(status) {
517 // set the phrase and coin
518 var expected = "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
519 page.evaluate(function() {
520 $(".phrase").val("abandon abandon ability");
521 $(".phrase").trigger("input");
522 $(".network option[selected]").removeAttr("selected");
523 $(".network option").filter(function() {
524 return $(this).html() == "Jumbucks";
525 }).prop("selected", true);
526 $(".network").trigger("change");
527 });
528 // check the address is generated correctly
529 waitForGenerate(function() {
530 var actual = page.evaluate(function() {
531 return $(".address:first").text();
532 });
533 if (actual != expected) {
534 console.log("Jumbucks address is incorrect");
535 console.log("Expected: " + expected);
536 console.log("Actual: " + actual);
537 fail();
538 }
539 next();
540 });
541 });
542 },
543
544 // Network can be set to clam
545 function() {
546 page.open(url, function(status) {
547 // set the phrase and coin
548 var expected = "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
549 page.evaluate(function() {
550 $(".phrase").val("abandon abandon ability");
551 $(".phrase").trigger("input");
552 $(".network option[selected]").removeAttr("selected");
553 $(".network option").filter(function() {
554 return $(this).html() == "CLAM";
555 }).prop("selected", true);
556 $(".network").trigger("change");
557 });
558 // check the address is generated correctly
559 waitForGenerate(function() {
560 var actual = page.evaluate(function() {
561 return $(".address:first").text();
562 });
563 if (actual != expected) {
564 console.log("CLAM address is incorrect");
565 console.log("Expected: " + expected);
566 console.log("Actual: " + actual);
567 fail();
568 }
569 next();
570 });
571 });
572 },
573
574 // Network can be set to dash
575 function() {
576 page.open(url, function(status) {
577 // set the phrase and coin
578 var expected = "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
579 page.evaluate(function() {
580 $(".phrase").val("abandon abandon ability");
581 $(".phrase").trigger("input");
582 $(".network option[selected]").removeAttr("selected");
583 $(".network option").filter(function() {
584 return $(this).html() == "DASH";
585 }).prop("selected", true);
586 $(".network").trigger("change");
587 });
588 // check the address is generated correctly
589 waitForGenerate(function() {
590 var actual = page.evaluate(function() {
591 return $(".address:first").text();
592 });
593 if (actual != expected) {
594 console.log("DASH address is incorrect");
595 console.log("Expected: " + expected);
596 console.log("Actual: " + actual);
597 fail();
598 }
599 next();
600 });
601 });
602 },
603
604 // Network can be set to game
605 function() {
606 page.open(url, function(status) {
607 // set the phrase and coin
608 var expected = "GSMY9bAp36cMR4zyT4uGVS7GFjpdXbao5Q";
609 page.evaluate(function() {
610 $(".phrase").val("abandon abandon ability");
611 $(".phrase").trigger("input");
612 $(".network option[selected]").removeAttr("selected");
613 $(".network option").filter(function() {
614 return $(this).html() == "GAME";
615 }).prop("selected", true);
616 $(".network").trigger("change");
617 });
618 // check the address is generated correctly
619 waitForGenerate(function() {
620 var actual = page.evaluate(function() {
621 return $(".address:first").text();
622 });
623 if (actual != expected) {
624 console.log("GAME address is incorrect");
625 console.log("Expected: " + expected);
626 console.log("Actual: " + actual);
627 fail();
628 }
629 next();
630 });
631 });
632 },
633
634 // Network can be set to namecoin
635 function() {
636 page.open(url, function(status) {
637 // set the phrase and coin
638 var expected = "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
639 page.evaluate(function() {
640 $(".phrase").val("abandon abandon ability");
641 $(".phrase").trigger("input");
642 $(".network option[selected]").removeAttr("selected");
643 $(".network option").filter(function() {
644 return $(this).html() == "Namecoin";
645 }).prop("selected", true);
646 $(".network").trigger("change");
647 });
648 // check the address is generated correctly
649 waitForGenerate(function() {
650 var actual = page.evaluate(function() {
651 return $(".address:first").text();
652 });
653 if (actual != expected) {
654 console.log("Namecoin address is incorrect");
655 console.log("Expected: " + expected);
656 console.log("Actual: " + actual);
657 fail();
658 }
659 next();
660 });
661 });
662 },
663
664 // Network can be set to peercoin
665 function() {
666 page.open(url, function(status) {
667 // set the phrase and coin
668 var expected = "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
669 page.evaluate(function() {
670 $(".phrase").val("abandon abandon ability");
671 $(".phrase").trigger("input");
672 $(".network option[selected]").removeAttr("selected");
673 $(".network option").filter(function() {
674 return $(this).html() == "Peercoin";
675 }).prop("selected", true);
676 $(".network").trigger("change");
677 });
678 // check the address is generated correctly
679 waitForGenerate(function() {
680 var actual = page.evaluate(function() {
681 return $(".address:first").text();
682 });
683 if (actual != expected) {
684 console.log("Peercoin address is incorrect");
685 console.log("Expected: " + expected);
686 console.log("Actual: " + actual);
687 fail();
688 }
689 next();
690 });
691 });
692 },
693
694 // Network can be set to ethereum
695 function() {
696
697 page.open(url, function(status) {
698
699 // set the phrase and coin
700 page.evaluate(function() {
701 $(".phrase").val("abandon abandon ability");
702 $(".phrase").trigger("input");
703 $(".network option[selected]").removeAttr("selected");
704 $(".network option").filter(function() {
705 return $(this).html() == "Ethereum";
706 }).prop("selected", true);
707 $(".network").trigger("change");
708 });
709 waitForGenerate(function() {
710 // check the address is generated correctly
711 // this value comes from
712 // https://www.myetherwallet.com/#view-wallet-info
713 // Unusual capitalization is due to checksum
714 var expected = "0xe5815d5902Ad612d49283DEdEc02100Bd44C2772";
715 var actual = page.evaluate(function() {
716 return $(".address:first").text();
717 });
718 if (actual != expected) {
719 console.log("Ethereum address is incorrect");
720 console.log("Expected: " + expected);
721 console.log("Actual: " + actual);
722 fail();
723 }
724 // check the private key is correct
725 // this private key can be imported into
726 // https://www.myetherwallet.com/#view-wallet-info
727 // and it should correlate to the address above
728 var expected = "8f253078b73d7498302bb78c171b23ce7a8fb511987d2b2702b731638a4a15e7";
729 var actual = page.evaluate(function() {
730 return $(".privkey:first").text();
731 });
732 if (actual != expected) {
733 console.log("Ethereum privkey is incorrect");
734 console.log("Expected: " + expected);
735 console.log("Actual: " + actual);
736 fail();
737 }
738 // check the public key is correct
739 // TODO
740 // don't have any third-party source to generate the expected value
741 //var expected = "?";
742 //var actual = page.evaluate(function() {
743 // return $(".pubkey:first").text();
744 //});
745 //if (actual != expected) {
746 // console.log("Ethereum privkey is incorrect");
747 // console.log("Expected: " + expected);
748 // console.log("Actual: " + actual);
749 // fail();
750 //}
751 next();
752 });
753 });
754 },
755
756 // BIP39 seed is set from phrase
757 function() {
758 page.open(url, function(status) {
759 // set the phrase
760 var expected = "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
761 page.evaluate(function() {
762 $(".phrase").val("abandon abandon ability");
763 $(".phrase").trigger("input");
764 });
765 // check the address is generated correctly
766 waitForGenerate(function() {
767 var actual = page.evaluate(function() {
768 return $(".seed").val();
769 });
770 if (actual != expected) {
771 console.log("BIP39 seed is incorrectly generated from mnemonic");
772 console.log("Expected: " + expected);
773 console.log("Actual: " + actual);
774 fail();
775 }
776 next();
777 });
778 });
779 },
780
781 // BIP32 root key is set from phrase
782 function() {
783 page.open(url, function(status) {
784 // set the phrase
785 var expected = "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
786 page.evaluate(function() {
787 $(".phrase").val("abandon abandon ability");
788 $(".phrase").trigger("input");
789 });
790 // check the address is generated correctly
791 waitForGenerate(function() {
792 var actual = page.evaluate(function() {
793 return $(".root-key").val();
794 });
795 if (actual != expected) {
796 console.log("Root key is incorrectly generated from mnemonic");
797 console.log("Expected: " + expected);
798 console.log("Actual: " + actual);
799 fail();
800 }
801 next();
802 });
803 });
804 },
805
806 // Tabs show correct addresses when changed
807 function() {
808 page.open(url, function(status) {
809 // set the phrase
810 var expected = "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
811 page.evaluate(function() {
812 $(".phrase").val("abandon abandon ability");
813 $(".phrase").trigger("input");
814 });
815 // change tabs
816 waitForGenerate(function() {
817 page.evaluate(function() {
818 $("#bip32-tab a").click();
819 });
820 // check the address is generated correctly
821 waitForGenerate(function() {
822 var actual = page.evaluate(function() {
823 return $(".address:first").text();
824 });
825 if (actual != expected) {
826 console.log("Clicking tab generates incorrect address");
827 console.log("Expected: " + expected);
828 console.log("Actual: " + actual);
829 fail();
830 }
831 next();
832 });
833 });
834 });
835 },
836
837 // BIP44 derivation path is shown
838 function() {
839 page.open(url, function(status) {
840 // set the phrase
841 var expected = "m/44'/0'/0'/0";
842 page.evaluate(function() {
843 $(".phrase").val("abandon abandon ability");
844 $(".phrase").trigger("input");
845 });
846 // check the derivation path of the first address
847 waitForGenerate(function() {
848 var actual = page.evaluate(function() {
849 return $("#bip44 .path").val();
850 });
851 if (actual != expected) {
852 console.log("BIP44 derivation path is incorrect");
853 console.log("Expected: " + expected);
854 console.log("Actual: " + actual);
855 fail();
856 }
857 next();
858 });
859 });
860 },
861
862 // BIP44 extended private key is shown
863 function() {
864 page.open(url, function(status) {
865 // set the phrase
866 var expected = "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
867 page.evaluate(function() {
868 $(".phrase").val("abandon abandon ability");
869 $(".phrase").trigger("input");
870 });
871 // check the BIP44 extended private key
872 waitForGenerate(function() {
873 var actual = page.evaluate(function() {
874 return $(".extended-priv-key").val();
875 });
876 if (actual != expected) {
877 console.log("BIP44 extended private key is incorrect");
878 console.log("Expected: " + expected);
879 console.log("Actual: " + actual);
880 fail();
881 }
882 next();
883 });
884 });
885 },
886
887 // BIP44 extended public key is shown
888 function() {
889 page.open(url, function(status) {
890 // set the phrase
891 var expected = "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
892 page.evaluate(function() {
893 $(".phrase").val("abandon abandon ability");
894 $(".phrase").trigger("input");
895 });
896 // check the BIP44 extended public key
897 waitForGenerate(function() {
898 var actual = page.evaluate(function() {
899 return $(".extended-pub-key").val();
900 });
901 if (actual != expected) {
902 console.log("BIP44 extended public key is incorrect");
903 console.log("Expected: " + expected);
904 console.log("Actual: " + actual);
905 fail();
906 }
907 next();
908 });
909 });
910 },
911
912 // BIP44 purpose field changes address list
913 function() {
914 page.open(url, function(status) {
915 // set the phrase
916 var expected = "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
917 page.evaluate(function() {
918 $(".phrase").val("abandon abandon ability");
919 $(".phrase").trigger("input");
920 });
921 waitForGenerate(function() {
922 // change the bip44 purpose field to 45
923 page.evaluate(function() {
924 $("#bip44 .purpose").val("45");
925 $("#bip44 .purpose").trigger("input");
926 });
927 waitForGenerate(function() {
928 // check the address for the new derivation path
929 var actual = page.evaluate(function() {
930 return $(".address:first").text();
931 });
932 if (actual != expected) {
933 console.log("BIP44 purpose field generates incorrect address");
934 console.log("Expected: " + expected);
935 console.log("Actual: " + actual);
936 fail();
937 }
938 next();
939 });
940 });
941 });
942 },
943
944 // BIP44 coin field changes address list
945 function() {
946 page.open(url, function(status) {
947 // set the phrase
948 var expected = "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
949 page.evaluate(function() {
950 $(".phrase").val("abandon abandon ability");
951 $(".phrase").trigger("input");
952 });
953 waitForGenerate(function() {
954 // change the bip44 purpose field to 45
955 page.evaluate(function() {
956 $("#bip44 .coin").val("1");
957 $("#bip44 .coin").trigger("input");
958 });
959 waitForGenerate(function() {
960 // check the address for the new derivation path
961 var actual = page.evaluate(function() {
962 return $(".address:first").text();
963 });
964 if (actual != expected) {
965 console.log("BIP44 coin field generates incorrect address");
966 console.log("Expected: " + expected);
967 console.log("Actual: " + actual);
968 fail();
969 }
970 next();
971 });
972 });
973 });
974 },
975
976 // BIP44 account field changes address list
977 function() {
978 page.open(url, function(status) {
979 // set the phrase
980 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
981 page.evaluate(function() {
982 $(".phrase").val("abandon abandon ability");
983 $(".phrase").trigger("input");
984 });
985 waitForGenerate(function() {
986 // change the bip44 purpose field to 45
987 page.evaluate(function() {
988 $("#bip44 .account").val("1");
989 $("#bip44 .account").trigger("input");
990 });
991 waitForGenerate(function() {
992 // check the address for the new derivation path
993 var actual = page.evaluate(function() {
994 return $(".address:first").text();
995 });
996 if (actual != expected) {
997 console.log("BIP44 account field generates incorrect address");
998 console.log("Expected: " + expected);
999 console.log("Actual: " + actual);
1000 fail();
1001 }
1002 next();
1003 });
1004 });
1005 });
1006 },
1007
1008 // BIP44 change field changes address list
1009 function() {
1010 page.open(url, function(status) {
1011 // set the phrase
1012 var expected = "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
1013 page.evaluate(function() {
1014 $(".phrase").val("abandon abandon ability");
1015 $(".phrase").trigger("input");
1016 });
1017 waitForGenerate(function() {
1018 // change the bip44 purpose field to 45
1019 page.evaluate(function() {
1020 $("#bip44 .change").val("1");
1021 $("#bip44 .change").trigger("input");
1022 });
1023 waitForGenerate(function() {
1024 // check the address for the new derivation path
1025 var actual = page.evaluate(function() {
1026 return $(".address:first").text();
1027 });
1028 if (actual != expected) {
1029 console.log("BIP44 change field generates incorrect address");
1030 console.log("Expected: " + expected);
1031 console.log("Actual: " + actual);
1032 fail();
1033 }
1034 next();
1035 });
1036 });
1037 });
1038 },
1039
1040 // BIP32 derivation path can be set
1041 function() {
1042 page.open(url, function(status) {
1043 // set the phrase
1044 var expected = "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
1045 page.evaluate(function() {
1046 $(".phrase").val("abandon abandon ability");
1047 $(".phrase").trigger("input");
1048 });
1049 // change tabs
1050 waitForGenerate(function() {
1051 page.evaluate(function() {
1052 $("#bip32-tab a").click();
1053 });
1054 // set the derivation path to m/1
1055 waitForGenerate(function() {
1056 page.evaluate(function() {
1057 $("#bip32 .path").val("m/1");
1058 $("#bip32 .path").trigger("input");
1059 });
1060 // check the address is generated correctly
1061 waitForGenerate(function() {
1062 var actual = page.evaluate(function() {
1063 return $(".address:first").text();
1064 });
1065 if (actual != expected) {
1066 console.log("Custom BIP32 path generates incorrect address");
1067 console.log("Expected: " + expected);
1068 console.log("Actual: " + actual);
1069 fail();
1070 }
1071 next();
1072 });
1073 });
1074 });
1075 });
1076 },
1077
1078 // BIP32 can use hardened derivation paths
1079 function() {
1080 page.open(url, function(status) {
1081 // set the phrase
1082 var expected = "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
1083 page.evaluate(function() {
1084 $(".phrase").val("abandon abandon ability");
1085 $(".phrase").trigger("input");
1086 });
1087 // change tabs
1088 waitForGenerate(function() {
1089 page.evaluate(function() {
1090 $("#bip32-tab a").click();
1091 });
1092 // set the derivation path to m/0'
1093 waitForGenerate(function() {
1094 page.evaluate(function() {
1095 $("#bip32 .path").val("m/0'");
1096 $("#bip32 .path").trigger("input");
1097 });
1098 // check the address is generated correctly
1099 waitForGenerate(function() {
1100 var actual = page.evaluate(function() {
1101 return $(".address:first").text();
1102 });
1103 if (actual != expected) {
1104 console.log("Hardened BIP32 path generates incorrect address");
1105 console.log("Expected: " + expected);
1106 console.log("Actual: " + actual);
1107 fail();
1108 }
1109 next();
1110 });
1111 });
1112 });
1113 });
1114 },
1115
1116 // BIP32 extended private key is shown
1117 function() {
1118 page.open(url, function(status) {
1119 // set the phrase
1120 var expected = "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
1121 page.evaluate(function() {
1122 $(".phrase").val("abandon abandon ability");
1123 $(".phrase").trigger("input");
1124 });
1125 // change tabs
1126 waitForGenerate(function() {
1127 page.evaluate(function() {
1128 $("#bip32-tab a").click();
1129 });
1130 // check the extended private key is generated correctly
1131 waitForGenerate(function() {
1132 var actual = page.evaluate(function() {
1133 return $(".extended-priv-key").val();
1134 });
1135 if (actual != expected) {
1136 console.log("BIP32 extended private key is incorrect");
1137 console.log("Expected: " + expected);
1138 console.log("Actual: " + actual);
1139 fail();
1140 }
1141 next();
1142 });
1143 });
1144 });
1145 },
1146
1147 // BIP32 extended public key is shown
1148 function() {
1149 page.open(url, function(status) {
1150 // set the phrase
1151 var expected = "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1152 page.evaluate(function() {
1153 $(".phrase").val("abandon abandon ability");
1154 $(".phrase").trigger("input");
1155 });
1156 // change tabs
1157 waitForGenerate(function() {
1158 page.evaluate(function() {
1159 $("#bip32-tab a").click();
1160 });
1161 // check the extended public key is generated correctly
1162 waitForGenerate(function() {
1163 var actual = page.evaluate(function() {
1164 return $(".extended-pub-key").val();
1165 });
1166 if (actual != expected) {
1167 console.log("BIP32 extended public key is incorrect");
1168 console.log("Expected: " + expected);
1169 console.log("Actual: " + actual);
1170 fail();
1171 }
1172 next();
1173 });
1174 });
1175 });
1176 },
1177
1178 // Derivation path is shown in table
1179 function() {
1180 page.open(url, function(status) {
1181 // set the phrase
1182 var expected = "m/44'/0'/0'/0/0";
1183 page.evaluate(function() {
1184 $(".phrase").val("abandon abandon ability");
1185 $(".phrase").trigger("input");
1186 });
1187 // check for derivation path in table
1188 waitForGenerate(function() {
1189 var actual = page.evaluate(function() {
1190 return $(".index:first").text();
1191 });
1192 if (actual != expected) {
1193 console.log("Derivation path shown incorrectly in table");
1194 console.log("Expected: " + expected);
1195 console.log("Actual: " + actual);
1196 fail();
1197 }
1198 next();
1199 });
1200 });
1201 },
1202
1203 // Derivation path for address can be hardened
1204 function() {
1205 page.open(url, function(status) {
1206 // set the phrase
1207 var expected = "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1208 page.evaluate(function() {
1209 $(".phrase").val("abandon abandon ability");
1210 $(".phrase").trigger("input");
1211 });
1212 // change tabs
1213 waitForGenerate(function() {
1214 page.evaluate(function() {
1215 $("#bip32-tab a").click();
1216 });
1217 waitForGenerate(function() {
1218 // select the hardened addresses option
1219 page.evaluate(function() {
1220 $(".hardened-addresses").prop("checked", true);
1221 $(".hardened-addresses").trigger("change");
1222 });
1223 waitForGenerate(function() {
1224 // check the generated address is hardened
1225 var actual = page.evaluate(function() {
1226 return $(".address:first").text();
1227 });
1228 if (actual != expected) {
1229 console.log("Hardened address is incorrect");
1230 console.log("Expected: " + expected);
1231 console.log("Actual: " + actual);
1232 fail();
1233 }
1234 next();
1235 });
1236 });
1237 });
1238 });
1239 },
1240
1241 // Derivation path visibility can be toggled
1242 function() {
1243 page.open(url, function(status) {
1244 // set the phrase
1245 page.evaluate(function() {
1246 $(".phrase").val("abandon abandon ability");
1247 $(".phrase").trigger("input");
1248 });
1249 waitForGenerate(function() {
1250 // toggle path visibility
1251 page.evaluate(function() {
1252 $(".index-toggle").click();
1253 });
1254 // check the path is not visible
1255 var isInvisible = page.evaluate(function() {
1256 return $(".index:first span").hasClass("invisible");
1257 });
1258 if (!isInvisible) {
1259 console.log("Toggled derivation path is visible");
1260 fail();
1261 }
1262 next();
1263 });
1264 });
1265 },
1266
1267 // Address is shown
1268 function() {
1269 page.open(url, function(status) {
1270 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1271 // set the phrase
1272 page.evaluate(function() {
1273 $(".phrase").val("abandon abandon ability").trigger("input");
1274 });
1275 // get the address
1276 waitForGenerate(function() {
1277 var actual = page.evaluate(function() {
1278 return $(".address:first").text();
1279 });
1280 if (actual != expected) {
1281 console.log("Address is not shown");
1282 console.log("Expected: " + expected);
1283 console.log("Got: " + actual);
1284 fail();
1285 }
1286 next();
1287 });
1288 });
1289 },
1290
1291 // Addresses are shown in order of derivation path
1292 function() {
1293 page.open(url, function(status) {
1294 // set the phrase
1295 page.evaluate(function() {
1296 $(".phrase").val("abandon abandon ability").trigger("input");
1297 });
1298 // get the derivation paths
1299 waitForGenerate(function() {
1300 var paths = page.evaluate(function() {
1301 return $(".index").map(function(i, e) {
1302 return $(e).text();
1303 });
1304 });
1305 if (paths.length != 20) {
1306 console.log("Total paths is less than expected: " + paths.length);
1307 fail();
1308 }
1309 for (var i=0; i<paths.length; i++) {
1310 var expected = "m/44'/0'/0'/0/" + i;
1311 var actual = paths[i];
1312 if (actual != expected) {
1313 console.log("Path " + i + " is incorrect");
1314 console.log("Expected: " + expected);
1315 console.log("Actual: " + actual);
1316 fail();
1317 }
1318 }
1319 next();
1320 });
1321 });
1322 },
1323
1324 // Address visibility can be toggled
1325 function() {
1326 page.open(url, function(status) {
1327 // set the phrase
1328 page.evaluate(function() {
1329 $(".phrase").val("abandon abandon ability");
1330 $(".phrase").trigger("input");
1331 });
1332 waitForGenerate(function() {
1333 // toggle address visibility
1334 page.evaluate(function() {
1335 $(".address-toggle").click();
1336 });
1337 // check the address is not visible
1338 var isInvisible = page.evaluate(function() {
1339 return $(".address:first span").hasClass("invisible");
1340 });
1341 if (!isInvisible) {
1342 console.log("Toggled address is visible");
1343 fail();
1344 }
1345 next();
1346 });
1347 });
1348 },
1349
1350 // Public key is shown
1351 function() {
1352 page.open(url, function(status) {
1353 var expected = "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1354 // set the phrase
1355 page.evaluate(function() {
1356 $(".phrase").val("abandon abandon ability").trigger("input");
1357 });
1358 // get the address
1359 waitForGenerate(function() {
1360 var actual = page.evaluate(function() {
1361 return $(".pubkey:first").text();
1362 });
1363 if (actual != expected) {
1364 console.log("Public key is not shown");
1365 console.log("Expected: " + expected);
1366 console.log("Got: " + actual);
1367 fail();
1368 }
1369 next();
1370 });
1371 });
1372 },
1373
1374 // Public key visibility can be toggled
1375 function() {
1376 page.open(url, function(status) {
1377 // set the phrase
1378 page.evaluate(function() {
1379 $(".phrase").val("abandon abandon ability");
1380 $(".phrase").trigger("input");
1381 });
1382 waitForGenerate(function() {
1383 // toggle public key visibility
1384 page.evaluate(function() {
1385 $(".public-key-toggle").click();
1386 });
1387 // check the public key is not visible
1388 var isInvisible = page.evaluate(function() {
1389 return $(".pubkey:first span").hasClass("invisible");
1390 });
1391 if (!isInvisible) {
1392 console.log("Toggled public key is visible");
1393 fail();
1394 }
1395 next();
1396 });
1397 });
1398 },
1399
1400 // Private key is shown
1401 function() {
1402 page.open(url, function(status) {
1403 var expected = "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1404 // set the phrase
1405 page.evaluate(function() {
1406 $(".phrase").val("abandon abandon ability").trigger("input");
1407 });
1408 // get the address
1409 waitForGenerate(function() {
1410 var actual = page.evaluate(function() {
1411 return $(".privkey:first").text();
1412 });
1413 if (actual != expected) {
1414 console.log("Private key is not shown");
1415 console.log("Expected: " + expected);
1416 console.log("Got: " + actual);
1417 fail();
1418 }
1419 next();
1420 });
1421 });
1422 },
1423
1424 // Private key visibility can be toggled
1425 function() {
1426 page.open(url, function(status) {
1427 // set the phrase
1428 page.evaluate(function() {
1429 $(".phrase").val("abandon abandon ability");
1430 $(".phrase").trigger("input");
1431 });
1432 waitForGenerate(function() {
1433 // toggle private key visibility
1434 page.evaluate(function() {
1435 $(".private-key-toggle").click();
1436 });
1437 // check the private key is not visible
1438 var isInvisible = page.evaluate(function() {
1439 return $(".privkey:first span").hasClass("invisible");
1440 });
1441 if (!isInvisible) {
1442 console.log("Toggled private key is visible");
1443 fail();
1444 }
1445 next();
1446 });
1447 });
1448 },
1449
1450 // More addresses can be generated
1451 function() {
1452 page.open(url, function(status) {
1453 // set the phrase
1454 page.evaluate(function() {
1455 $(".phrase").val("abandon abandon ability");
1456 $(".phrase").trigger("input");
1457 });
1458 waitForGenerate(function() {
1459 // generate more addresses
1460 page.evaluate(function() {
1461 $(".more").click();
1462 });
1463 waitForGenerate(function() {
1464 // check there are more addresses
1465 var addressCount = page.evaluate(function() {
1466 return $(".address").length;
1467 });
1468 if (addressCount != 40) {
1469 console.log("More addresses cannot be generated");
1470 fail();
1471 }
1472 next();
1473 });
1474 });
1475 });
1476 },
1477
1478 // A custom number of additional addresses can be generated
1479 function() {
1480 page.open(url, function(status) {
1481 // set the phrase
1482 page.evaluate(function() {
1483 $(".phrase").val("abandon abandon ability");
1484 $(".phrase").trigger("input");
1485 });
1486 waitForGenerate(function() {
1487 // get the current number of addresses
1488 var oldAddressCount = page.evaluate(function() {
1489 return $(".address").length;
1490 });
1491 // set a custom number of additional addresses
1492 page.evaluate(function() {
1493 $(".rows-to-add").val(1);
1494 });
1495 // generate more addresses
1496 page.evaluate(function() {
1497 $(".more").click();
1498 });
1499 waitForGenerate(function() {
1500 // check there are the correct number of addresses
1501 var newAddressCount = page.evaluate(function() {
1502 return $(".address").length;
1503 });
1504 if (newAddressCount - oldAddressCount != 1) {
1505 console.log("Number of additional addresses cannot be customized");
1506 console.log(newAddressCount)
1507 console.log(oldAddressCount)
1508 fail();
1509 }
1510 next();
1511 });
1512 });
1513 });
1514 },
1515
1516 // Additional addresses are shown in order of derivation path
1517 function() {
1518 page.open(url, function(status) {
1519 // set the phrase
1520 page.evaluate(function() {
1521 $(".phrase").val("abandon abandon ability").trigger("input");
1522 });
1523 waitForGenerate(function() {
1524 // generate more addresses
1525 page.evaluate(function() {
1526 $(".more").click();
1527 });
1528 // get the derivation paths
1529 waitForGenerate(function() {
1530 var paths = page.evaluate(function() {
1531 return $(".index").map(function(i, e) {
1532 return $(e).text();
1533 });
1534 });
1535 if (paths.length != 40) {
1536 console.log("Total additional paths is less than expected: " + paths.length);
1537 fail();
1538 }
1539 for (var i=0; i<paths.length; i++) {
1540 var expected = "m/44'/0'/0'/0/" + i;
1541 var actual = paths[i];
1542 if (actual != expected) {
1543 console.log("Path " + i + " is not in correct order");
1544 console.log("Expected: " + expected);
1545 console.log("Actual: " + actual);
1546 fail();
1547 }
1548 }
1549 next();
1550 });
1551 });
1552 });
1553 },
1554
1555 // BIP32 root key can be set by the user
1556 function() {
1557 page.open(url, function(status) {
1558 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1559 // set the root key
1560 page.evaluate(function() {
1561 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1562 });
1563 waitForGenerate(function() {
1564 var actual = page.evaluate(function() {
1565 return $(".address:first").text();
1566 });
1567 if (actual != expected) {
1568 console.log("Setting BIP32 root key results in wrong address");
1569 console.log("Expected: " + expected);
1570 console.log("Actual: " + actual);
1571 fail();
1572 }
1573 next();
1574 });
1575 });
1576 },
1577
1578 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1579 function() {
1580 page.open(url, function(status) {
1581 var expected = "";
1582 // set a mnemonic
1583 page.evaluate(function() {
1584 $(".phrase").val("A non-blank but invalid value");
1585 });
1586 // Accept any confirm dialogs
1587 page.onConfirm = function() {
1588 return true;
1589 };
1590 // set the root key
1591 page.evaluate(function() {
1592 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1593 });
1594 waitForGenerate(function() {
1595 var actual = page.evaluate(function() {
1596 return $(".phrase").val();
1597 });
1598 if (actual != expected) {
1599 console.log("Phrase not cleared when setting BIP32 root key");
1600 console.log("Expected: " + expected);
1601 console.log("Actual: " + actual);
1602 fail();
1603 }
1604 next();
1605 });
1606 });
1607 },
1608
1609 // Clearing of phrase, passphrase and seed can be cancelled by user
1610 function() {
1611 page.open(url, function(status) {
1612 var expected = "abandon abandon ability";
1613 // set a mnemonic
1614 page.evaluate(function() {
1615 $(".phrase").val("abandon abandon ability");
1616 });
1617 // Cancel any confirm dialogs
1618 page.onConfirm = function() {
1619 return false;
1620 };
1621 // set the root key
1622 page.evaluate(function() {
1623 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1624 });
1625 var actual = page.evaluate(function() {
1626 return $(".phrase").val();
1627 });
1628 if (actual != expected) {
1629 console.log("Phrase not retained when cancelling changes to BIP32 root key");
1630 console.log("Expected: " + expected);
1631 console.log("Actual: " + actual);
1632 fail();
1633 }
1634 next();
1635 });
1636 },
1637
1638 // Custom BIP32 root key is used when changing the derivation path
1639 function() {
1640 page.open(url, function(status) {
1641 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1642 // set the root key
1643 page.evaluate(function() {
1644 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1645 });
1646 waitForGenerate(function() {
1647 // change the derivation path
1648 page.evaluate(function() {
1649 $("#account").val("1").trigger("input");
1650 });
1651 // check the bip32 root key is used for derivation, not the blank phrase
1652 waitForGenerate(function() {
1653 var actual = page.evaluate(function() {
1654 return $(".address:first").text();
1655 });
1656 if (actual != expected) {
1657 console.log("Changing the derivation path does not use BIP32 root key");
1658 console.log("Expected: " + expected);
1659 console.log("Actual: " + actual);
1660 fail();
1661 }
1662 next();
1663 });
1664 });
1665 });
1666 },
1667
1668 // Incorrect mnemonic shows error
1669 function() {
1670 page.open(url, function(status) {
1671 // set the root key
1672 page.evaluate(function() {
1673 $(".phrase").val("abandon abandon abandon").trigger("input");
1674 });
1675 waitForFeedback(function() {
1676 // check there is an error shown
1677 var feedback = page.evaluate(function() {
1678 return $(".feedback").text();
1679 });
1680 if (feedback.length <= 0) {
1681 console.log("Invalid mnemonic does not show error");
1682 fail();
1683 }
1684 next();
1685 });
1686 });
1687 },
1688
1689 // Incorrect word shows suggested replacement
1690 function() {
1691 page.open(url, function(status) {
1692 // set the root key
1693 page.evaluate(function() {
1694 $(".phrase").val("abandon abandon abiliti").trigger("input");
1695 });
1696 // check there is a suggestion shown
1697 waitForFeedback(function() {
1698 var feedback = page.evaluate(function() {
1699 return $(".feedback").text();
1700 });
1701 if (feedback.indexOf("did you mean ability?") < 0) {
1702 console.log("Incorrect word does not show suggested replacement");
1703 console.log("Error: " + error);
1704 fail();
1705 }
1706 next();
1707 });
1708 });
1709 },
1710
1711 // Github pull request 48
1712 // First four letters of word shows that word, not closest
1713 // since first four letters gives unique word in BIP39 wordlist
1714 // eg ille should show illegal, not idle
1715 function() {
1716 page.open(url, function(status) {
1717 // set the incomplete word
1718 page.evaluate(function() {
1719 $(".phrase").val("ille").trigger("input");
1720 });
1721 // check there is a suggestion shown
1722 waitForFeedback(function() {
1723 var feedback = page.evaluate(function() {
1724 return $(".feedback").text();
1725 });
1726 if (feedback.indexOf("did you mean illegal?") < 0) {
1727 console.log("Start of word does not show correct suggestion");
1728 console.log("Error: " + error);
1729 fail();
1730 }
1731 next();
1732 });
1733 });
1734 },
1735
1736 // Incorrect BIP32 root key shows error
1737 function() {
1738 page.open(url, function(status) {
1739 // set the root key
1740 page.evaluate(function() {
1741 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1742 });
1743 // check there is an error shown
1744 waitForFeedback(function() {
1745 var feedback = page.evaluate(function() {
1746 return $(".feedback").text();
1747 });
1748 if (feedback != "Invalid root key") {
1749 console.log("Invalid root key does not show error");
1750 console.log("Error: " + error);
1751 fail();
1752 }
1753 next();
1754 });
1755 });
1756 },
1757
1758 // Derivation path not starting with m shows error
1759 function() {
1760 page.open(url, function(status) {
1761 // set the mnemonic phrase
1762 page.evaluate(function() {
1763 $(".phrase").val("abandon abandon ability").trigger("input");
1764 });
1765 waitForGenerate(function() {
1766 // select the bip32 tab so custom derivation path can be set
1767 page.evaluate(function() {
1768 $("#bip32-tab a").click();
1769 });
1770 waitForGenerate(function() {
1771 // set the incorrect derivation path
1772 page.evaluate(function() {
1773 $("#bip32 .path").val("n/0").trigger("input");
1774 });
1775 waitForFeedback(function() {
1776 var feedback = page.evaluate(function() {
1777 return $(".feedback").text();
1778 });
1779 if (feedback != "First character must be 'm'") {
1780 console.log("Derivation path not starting with m should show error");
1781 console.log("Error: " + error);
1782 fail();
1783 }
1784 next();
1785 });
1786 });
1787 });
1788 });
1789 },
1790
1791 // Derivation path containing invalid characters shows useful error
1792 function() {
1793 page.open(url, function(status) {
1794 // set the mnemonic phrase
1795 page.evaluate(function() {
1796 $(".phrase").val("abandon abandon ability").trigger("input");
1797 });
1798 waitForGenerate(function() {
1799 // select the bip32 tab so custom derivation path can be set
1800 page.evaluate(function() {
1801 $("#bip32-tab a").click();
1802 });
1803 waitForGenerate(function() {
1804 // set the incorrect derivation path
1805 page.evaluate(function() {
1806 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1807 });
1808 waitForFeedback(function() {
1809 var feedback = page.evaluate(function() {
1810 return $(".feedback").text();
1811 });
1812 if (feedback != "Invalid characters 0wrong1 found at depth 2") {
1813 console.log("Derivation path with invalid characters should show error");
1814 console.log("Error: " + error);
1815 fail();
1816 }
1817 next();
1818 });
1819 });
1820 });
1821 });
1822 },
1823
1824 // Github Issue 11: Default word length is 15
1825 // https://github.com/iancoleman/bip39/issues/11
1826 function() {
1827 page.open(url, function(status) {
1828 // get the word length
1829 var defaultLength = page.evaluate(function() {
1830 return $(".strength").val();
1831 });
1832 if (defaultLength != 15) {
1833 console.log("Default word length is not 15");
1834 fail();
1835 }
1836 next();
1837 });
1838 },
1839
1840
1841 // Github Issue 12: Generate more rows with private keys hidden
1842 // https://github.com/iancoleman/bip39/issues/12
1843 function() {
1844 page.open(url, function(status) {
1845 // set the phrase
1846 page.evaluate(function() {
1847 $(".phrase").val("abandon abandon ability");
1848 $(".phrase").trigger("input");
1849 });
1850 waitForGenerate(function() {
1851 // toggle private keys hidden, then generate more addresses
1852 page.evaluate(function() {
1853 $(".private-key-toggle").click();
1854 $(".more").click();
1855 });
1856 waitForGenerate(function() {
1857 // check more have been generated
1858 var expected = 40;
1859 var numPrivKeys = page.evaluate(function() {
1860 return $(".privkey").length;
1861 });
1862 if (numPrivKeys != expected) {
1863 console.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1864 console.log("Expected: " + expected);
1865 console.log("Actual: " + numPrivKeys);
1866 fail();
1867 }
1868 // check no private keys are shown
1869 var numHiddenPrivKeys = page.evaluate(function() {
1870 return $(".privkey span[class=invisible]").length;
1871 });
1872 if (numHiddenPrivKeys != expected) {
1873 console.log("Generating more does not retain hidden state of privkeys");
1874 console.log("Expected: " + expected);
1875 console.log("Actual: " + numHiddenPrivKeys);
1876 fail();
1877 }
1878 next();
1879 });
1880 });
1881 });
1882 },
1883
1884 // Github Issue 19: Mnemonic is not sensitive to whitespace
1885 // https://github.com/iancoleman/bip39/issues/19
1886 function() {
1887 page.open(url, function(status) {
1888 // set the phrase
1889 var expected = "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1890 page.evaluate(function() {
1891 var doubleSpace = " ";
1892 $(".phrase").val("urge cat" + doubleSpace + "bid");
1893 $(".phrase").trigger("input");
1894 });
1895 waitForGenerate(function() {
1896 // Check the bip32 root key is correct
1897 var actual = page.evaluate(function() {
1898 return $(".root-key").val();
1899 });
1900 if (actual != expected) {
1901 console.log("Mnemonic is sensitive to whitespace");
1902 console.log("Expected: " + expected);
1903 console.log("Actual: " + actual);
1904 fail();
1905 }
1906 next();
1907 });
1908 });
1909 },
1910
1911 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1912 // https://github.com/iancoleman/bip39/issues/23
1913 function() {
1914 page.open(url, function(status) {
1915 // 1) and 2) set the phrase
1916 page.evaluate(function() {
1917 $(".phrase").val("abandon abandon ability").trigger("input");
1918 });
1919 waitForGenerate(function() {
1920 // 3) select bip32 tab
1921 page.evaluate(function() {
1922 $("#bip32-tab a").click();
1923 });
1924 waitForGenerate(function() {
1925 // 4) switch from bitcoin to litecoin
1926 page.evaluate(function() {
1927 $(".network option").filter(function() {
1928 return $(this).html() == "Litecoin";
1929 }).prop("selected", true);
1930 $(".network").trigger("change");
1931 });
1932 waitForGenerate(function() {
1933 // 5) Check derivation path is displayed correctly
1934 var expected = "m/0/0";
1935 var actual = page.evaluate(function() {
1936 return $(".index:first").text();
1937 });
1938 if (actual != expected) {
1939 console.log("Github Issue 23 Part 1: derivation path display error");
1940 console.log("Expected: " + expected);
1941 console.log("Actual: " + actual);
1942 fail();
1943 }
1944 // 5) Check address is displayed correctly
1945 var expected = "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1946 var actual = page.evaluate(function() {
1947 return $(".address:first").text();
1948 });
1949 if (actual != expected) {
1950 console.log("Github Issue 23 Part 1: address display error");
1951 console.log("Expected: " + expected);
1952 console.log("Actual: " + actual);
1953 fail();
1954 }
1955 next();
1956 });
1957 });
1958 });
1959 });
1960 },
1961
1962 // Github Issue 23 Part 2: Coin selection in derivation path
1963 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1964 function() {
1965 page.open(url, function(status) {
1966 // set the phrase
1967 page.evaluate(function() {
1968 $(".phrase").val("abandon abandon ability").trigger("input");
1969 });
1970 waitForGenerate(function() {
1971 // switch from bitcoin to clam
1972 page.evaluate(function() {
1973 $(".network option").filter(function() {
1974 return $(this).html() == "CLAM";
1975 }).prop("selected", true);
1976 $(".network").trigger("change");
1977 });
1978 waitForGenerate(function() {
1979 // check derivation path is displayed correctly
1980 var expected = "m/44'/23'/0'/0/0";
1981 var actual = page.evaluate(function() {
1982 return $(".index:first").text();
1983 });
1984 if (actual != expected) {
1985 console.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1986 console.log("Expected: " + expected);
1987 console.log("Actual: " + actual);
1988 fail();
1989 }
1990 next();
1991 });
1992 });
1993 });
1994 },
1995
1996 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1997 // https://github.com/iancoleman/bip39/issues/26
1998 function() {
1999 page.open(url, function(status) {
2000 // 1) 2) and 3) set the root key
2001 page.evaluate(function() {
2002 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
2003 });
2004 waitForGenerate(function() {
2005 // 4) switch from bitcoin to viacoin
2006 page.evaluate(function() {
2007 $(".network option").filter(function() {
2008 return $(this).html() == "Viacoin";
2009 }).prop("selected", true);
2010 $(".network").trigger("change");
2011 });
2012 waitForGenerate(function() {
2013 // 5) ensure the derived address is correct
2014 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
2015 var actual = page.evaluate(function() {
2016 return $(".address:first").text();
2017 });
2018 if (actual != expected) {
2019 console.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
2020 console.log("Expected: " + expected);
2021 console.log("Actual: " + actual);
2022 fail();
2023 }
2024 next();
2025 });
2026 });
2027 });
2028 },
2029
2030 // Selecting a language with no existing phrase should generate a phrase in
2031 // that language.
2032 function() {
2033 page.open(url, function(status) {
2034 // Select a language
2035 // Need to manually simulate hash being set due to quirk between
2036 // 'click' event triggered by javascript vs triggered by mouse.
2037 // Perhaps look into page.sendEvent
2038 // http://phantomjs.org/api/webpage/method/send-event.html
2039 page.evaluate(function() {
2040 window.location.hash = "#japanese";
2041 $("a[href='#japanese']").trigger("click");
2042 });
2043 waitForGenerate(function() {
2044 // Check the mnemonic is in Japanese
2045 var phrase = page.evaluate(function() {
2046 return $(".phrase").val();
2047 });
2048 if (phrase.length <= 0) {
2049 console.log("No Japanese phrase generated");
2050 fail();
2051 }
2052 if (phrase.charCodeAt(0) < 128) {
2053 console.log("First character of Japanese phrase is ascii");
2054 console.log("Phrase: " + phrase);
2055 fail();
2056 }
2057 next();
2058 });
2059 });
2060 },
2061
2062 // Selecting a language with existing phrase should update the phrase to use
2063 // that language.
2064 function() {
2065 page.open(url, function(status) {
2066 // Set the phrase to an English phrase.
2067 page.evaluate(function() {
2068 $(".phrase").val("abandon abandon ability").trigger("input");
2069 });
2070 waitForGenerate(function() {
2071 // Change to Italian
2072 // Need to manually simulate hash being set due to quirk between
2073 // 'click' event triggered by javascript vs triggered by mouse.
2074 // Perhaps look into page.sendEvent
2075 // http://phantomjs.org/api/webpage/method/send-event.html
2076 page.evaluate(function() {
2077 window.location.hash = "#italian";
2078 $("a[href='#italian']").trigger("click");
2079 });
2080 waitForGenerate(function() {
2081 // Check only the language changes, not the phrase
2082 var expected = "abaco abaco abbaglio";
2083 var actual = page.evaluate(function() {
2084 return $(".phrase").val();
2085 });
2086 if (actual != expected) {
2087 console.log("Changing language with existing phrase");
2088 console.log("Expected: " + expected);
2089 console.log("Actual: " + actual);
2090 fail();
2091 }
2092 // Check the address is correct
2093 var expected = "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
2094 var actual = page.evaluate(function() {
2095 return $(".address:first").text();
2096 });
2097 if (actual != expected) {
2098 console.log("Changing language generates incorrect address");
2099 console.log("Expected: " + expected);
2100 console.log("Actual: " + actual);
2101 fail();
2102 }
2103 next();
2104 });
2105 });
2106 });
2107 },
2108
2109 // Suggested replacement for erroneous word in non-English language
2110 function() {
2111 page.open(url, function(status) {
2112 // Set an incorrect phrase in Italian
2113 page.evaluate(function() {
2114 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
2115 });
2116 waitForFeedback(function() {
2117 // Check the suggestion is correct
2118 var feedback = page.evaluate(function() {
2119 return $(".feedback").text();
2120 });
2121 if (feedback.indexOf("did you mean abbaglio?") < 0) {
2122 console.log("Incorrect Italian word does not show suggested replacement");
2123 console.log("Error: " + error);
2124 fail();
2125 }
2126 next();
2127 });
2128 });
2129 },
2130
2131
2132 // Japanese word does not break across lines.
2133 // Point 2 from
2134 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
2135 function() {
2136 page.open(url, function(status) {
2137 hasWordBreakCss = page.content.indexOf("word-break: keep-all;") > -1;
2138 if (!hasWordBreakCss) {
2139 console.log("Japanese words can break across lines mid-word");
2140 console.log("Check CSS for '.phrase { word-break: keep-all; }'");
2141 fail();
2142 }
2143 // Run the next test
2144 next();
2145 });
2146 },
2147
2148 // Language can be specified at page load using hash value in url
2149 function() {
2150 page.open(url, function(status) {
2151 // Set the page hash as if it were on a fresh page load
2152 page.evaluate(function() {
2153 window.location.hash = "#japanese";
2154 });
2155 // Generate a random phrase
2156 page.evaluate(function() {
2157 $(".generate").trigger("click");
2158 });
2159 waitForGenerate(function() {
2160 // Check the phrase is in Japanese
2161 var phrase = page.evaluate(function() {
2162 return $(".phrase").val();
2163 });
2164 if (phrase.length <= 0) {
2165 console.log("No phrase generated using url hash");
2166 fail();
2167 }
2168 if (phrase.charCodeAt(0) < 128) {
2169 console.log("Language not detected from url hash on page load.");
2170 console.log("Phrase: " + phrase);
2171 fail();
2172 }
2173 next();
2174 });
2175 });
2176 },
2177
2178 // Entropy unit tests
2179 function() {
2180 page.open(url, function(status) {
2181 var response = page.evaluate(function() {
2182 var e;
2183 // binary entropy is detected
2184 try {
2185 e = Entropy.fromString("01010101");
2186 if (e.base.str != "binary") {
2187 return "Binary entropy not detected correctly";
2188 }
2189 }
2190 catch (e) {
2191 return e.message;
2192 }
2193 // base6 entropy is detected
2194 try {
2195 e = Entropy.fromString("012345012345");
2196 if (e.base.str != "base 6") {
2197 return "base6 entropy not detected correctly";
2198 }
2199 }
2200 catch (e) {
2201 return e.message;
2202 }
2203 // dice entropy is detected
2204 try {
2205 e = Entropy.fromString("123456123456");
2206 if (e.base.str != "base 6 (dice)") {
2207 return "dice entropy not detected correctly";
2208 }
2209 }
2210 catch (e) {
2211 return e.message;
2212 }
2213 // base10 entropy is detected
2214 try {
2215 e = Entropy.fromString("0123456789");
2216 if (e.base.str != "base 10") {
2217 return "base10 entropy not detected correctly";
2218 }
2219 }
2220 catch (e) {
2221 return e.message;
2222 }
2223 // hex entropy is detected
2224 try {
2225 e = Entropy.fromString("0123456789ABCDEF");
2226 if (e.base.str != "hexadecimal") {
2227 return "hexadecimal entropy not detected correctly";
2228 }
2229 }
2230 catch (e) {
2231 return e.message;
2232 }
2233 // card entropy is detected
2234 try {
2235 e = Entropy.fromString("AC4DTHKS");
2236 if (e.base.str != "card") {
2237 return "card entropy not detected correctly";
2238 }
2239 }
2240 catch (e) {
2241 return e.message;
2242 }
2243 // entropy is case insensitive
2244 try {
2245 e = Entropy.fromString("aBcDeF");
2246 if (e.cleanStr != "aBcDeF") {
2247 return "Entropy should not be case sensitive";
2248 }
2249 }
2250 catch (e) {
2251 return e.message;
2252 }
2253 // dice entropy is converted to base6
2254 try {
2255 e = Entropy.fromString("123456");
2256 if (e.cleanStr != "123450") {
2257 return "Dice entropy is not automatically converted to base6";
2258 }
2259 }
2260 catch (e) {
2261 return e.message;
2262 }
2263 // dice entropy is preferred to base6 if ambiguous
2264 try {
2265 e = Entropy.fromString("12345");
2266 if (e.base.str != "base 6 (dice)") {
2267 return "dice not used as default over base 6";
2268 }
2269 }
2270 catch (e) {
2271 return e.message;
2272 }
2273 // unused characters are ignored
2274 try {
2275 e = Entropy.fromString("fghijkl");
2276 if (e.cleanStr != "f") {
2277 return "additional characters are not ignored";
2278 }
2279 }
2280 catch (e) {
2281 return e.message;
2282 }
2283 // the lowest base is used by default
2284 // 7 could be decimal or hexadecimal, but should be detected as decimal
2285 try {
2286 e = Entropy.fromString("7");
2287 if (e.base.str != "base 10") {
2288 return "lowest base is not used";
2289 }
2290 }
2291 catch (e) {
2292 return e.message;
2293 }
2294 // Leading zeros are retained
2295 try {
2296 e = Entropy.fromString("000A");
2297 if (e.cleanStr != "000A") {
2298 return "Leading zeros are not retained";
2299 }
2300 }
2301 catch (e) {
2302 return e.message;
2303 }
2304 // Leading zeros are correctly preserved for hex in binary string
2305 try {
2306 e = Entropy.fromString("2A");
2307 if (e.binaryStr != "00101010") {
2308 return "Hex leading zeros are not correct in binary";
2309 }
2310 }
2311 catch (e) {
2312 return e.message;
2313 }
2314 // Leading zeros for base 6 as binary string
2315 // 20 = 2 events at 2.58 bits per event = 5 bits
2316 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2317 // so it needs 1 bit of padding to be the right bit length
2318 try {
2319 e = Entropy.fromString("20");
2320 if (e.binaryStr != "01100") {
2321 return "Base 6 as binary has leading zeros";
2322 }
2323 }
2324 catch (e) {
2325 return e.message;
2326 }
2327 // Leading zeros for base 10 as binary string
2328 try {
2329 e = Entropy.fromString("17");
2330 if (e.binaryStr != "010001") {
2331 return "Base 10 as binary has leading zeros";
2332 }
2333 }
2334 catch (e) {
2335 return e.message;
2336 }
2337 // Leading zeros for card entropy as binary string.
2338 // Card entropy is hashed so 2c does not necessarily produce leading zeros.
2339 try {
2340 e = Entropy.fromString("2c");
2341 if (e.binaryStr != "0010") {
2342 return "Card entropy as binary has leading zeros";
2343 }
2344 }
2345 catch (e) {
2346 return e.message;
2347 }
2348 // Keyboard mashing results in weak entropy
2349 // Despite being a long string, it's less than 30 bits of entropy
2350 try {
2351 e = Entropy.fromString("aj;se ifj; ask,dfv js;ifj");
2352 if (e.binaryStr.length >= 30) {
2353 return "Keyboard mashing should produce weak entropy";
2354 }
2355 }
2356 catch (e) {
2357 return e.message;
2358 }
2359 // Card entropy is used if every pair could be a card
2360 try {
2361 e = Entropy.fromString("4c3c2c");
2362 if (e.base.str != "card") {
2363 return "Card entropy not used if all pairs are cards";
2364 }
2365 }
2366 catch (e) {
2367 return e.message;
2368 }
2369 // Card entropy uses base 52
2370 // [ cards, binary ]
2371 try {
2372 var cards = [
2373 [ "ac", "0101" ],
2374 [ "acqs", "11011100" ],
2375 [ "acks", "01011100" ],
2376 [ "2cac", "11111000" ],
2377 [ "2c", "0010" ],
2378 [ "3d", "0001" ],
2379 [ "4h", "1001" ],
2380 [ "5s", "1001" ],
2381 [ "6c", "0000" ],
2382 [ "7d", "0001" ],
2383 [ "8h", "1011" ],
2384 [ "9s", "0010" ],
2385 [ "tc", "1001" ],
2386 [ "jd", "1111" ],
2387 [ "qh", "0010" ],
2388 [ "ks", "0101" ],
2389 [ "ks2c", "01010100" ],
2390 [ "KS2C", "01010100" ],
2391 ];
2392 for (var i=0; i<cards.length; i++) {
2393 var card = cards[i][0];
2394 var result = cards[i][1];
2395 e = Entropy.fromString(card);
2396 console.log(e.binary + " " + result);
2397 if (e.binaryStr !== result) {
2398 return "card entropy " + card + " not parsed correctly: " + result + " != " + e.binaryStr;
2399 }
2400 }
2401 }
2402 catch (e) {
2403 return e.message;
2404 }
2405 return "PASS";
2406 });
2407 if (response != "PASS") {
2408 console.log("Entropy unit tests");
2409 console.log(response);
2410 fail();
2411 };
2412 next();
2413 });
2414 },
2415
2416 // Entropy can be entered by the user
2417 function() {
2418 page.open(url, function(status) {
2419 expected = {
2420 mnemonic: "abandon abandon ability",
2421 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2422 }
2423 // use entropy
2424 page.evaluate(function() {
2425 $(".use-entropy").prop("checked", true).trigger("change");
2426 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2427 });
2428 // check the mnemonic is set and address is correct
2429 waitForGenerate(function() {
2430 var actual = page.evaluate(function() {
2431 return {
2432 address: $(".address:first").text(),
2433 mnemonic: $(".phrase").val(),
2434 }
2435 });
2436 if (actual.mnemonic != expected.mnemonic) {
2437 console.log("Entropy does not generate correct mnemonic");
2438 console.log("Expected: " + expected.mnemonic);
2439 console.log("Got: " + actual.mnemonic);
2440 fail();
2441 }
2442 if (actual.address != expected.address) {
2443 console.log("Entropy does not generate correct address");
2444 console.log("Expected: " + expected.address);
2445 console.log("Got: " + actual.address);
2446 fail();
2447 }
2448 next();
2449 });
2450 });
2451 },
2452
2453 // A warning about entropy is shown to the user, with additional information
2454 function() {
2455 page.open(url, function(status) {
2456 // get text content from entropy sections of page
2457 var hasWarning = page.evaluate(function() {
2458 var entropyText = $(".entropy-container").text();
2459 var warning = "mnemonic may be insecure";
2460 if (entropyText.indexOf(warning) == -1) {
2461 return false;
2462 }
2463 var readMoreText = $("#entropy-notes").parent().text();
2464 var goodSources = "flipping a fair coin, rolling a fair dice, noise measurements etc";
2465 if (readMoreText.indexOf(goodSources) == -1) {
2466 return false;
2467 }
2468 return true;
2469 });
2470 // check the warnings and information are shown
2471 if (!hasWarning) {
2472 console.log("Page does not contain warning about using own entropy");
2473 fail();
2474 }
2475 next();
2476 });
2477 },
2478
2479 // The types of entropy available are described to the user
2480 function() {
2481 page.open(url, function(status) {
2482 // get placeholder text for entropy field
2483 var placeholder = page.evaluate(function() {
2484 return $(".entropy").attr("placeholder");
2485 });
2486 var options = [
2487 "binary",
2488 "base 6",
2489 "dice",
2490 "base 10",
2491 "hexadecimal",
2492 "cards",
2493 ];
2494 for (var i=0; i<options.length; i++) {
2495 var option = options[i];
2496 if (placeholder.indexOf(option) == -1) {
2497 console.log("Available entropy type is not shown to user: " + option);
2498 fail();
2499 }
2500 }
2501 next();
2502 });
2503 },
2504
2505 // The actual entropy used is shown to the user
2506 function() {
2507 page.open(url, function(status) {
2508 // use entropy
2509 var badEntropySource = page.evaluate(function() {
2510 var entropy = "Not A Very Good Entropy Source At All";
2511 $(".use-entropy").prop("checked", true).trigger("change");
2512 $(".entropy").val(entropy).trigger("input");
2513 });
2514 // check the actual entropy being used is shown
2515 waitForEntropyFeedback(function() {
2516 var expectedText = "AedEceAA";
2517 var entropyText = page.evaluate(function() {
2518 return $(".entropy-container").text();
2519 });
2520 if (entropyText.indexOf(expectedText) == -1) {
2521 console.log("Actual entropy used is not shown");
2522 fail();
2523 }
2524 next();
2525 });
2526 });
2527 },
2528
2529 // Binary entropy can be entered
2530 function() {
2531 page.open(url, function(status) {
2532 // use entropy
2533 page.evaluate(function() {
2534 $(".use-entropy").prop("checked", true).trigger("change");
2535 $(".entropy").val("01").trigger("input");
2536 });
2537 // check the entropy is shown to be the correct type
2538 waitForEntropyFeedback(function() {
2539 var entropyText = page.evaluate(function() {
2540 return $(".entropy-container").text();
2541 });
2542 if (entropyText.indexOf("binary") == -1) {
2543 console.log("Binary entropy is not detected and presented to user");
2544 fail();
2545 }
2546 next();
2547 });
2548 });
2549 },
2550
2551 // Base 6 entropy can be entered
2552 function() {
2553 page.open(url, function(status) {
2554 // use entropy
2555 page.evaluate(function() {
2556 $(".use-entropy").prop("checked", true).trigger("change");
2557 $(".entropy").val("012345").trigger("input");
2558 });
2559 // check the entropy is shown to be the correct type
2560 waitForEntropyFeedback(function() {
2561 var entropyText = page.evaluate(function() {
2562 return $(".entropy-container").text();
2563 });
2564 if (entropyText.indexOf("base 6") == -1) {
2565 console.log("Base 6 entropy is not detected and presented to user");
2566 fail();
2567 }
2568 next();
2569 });
2570 });
2571 },
2572
2573 // Base 6 dice entropy can be entered
2574 function() {
2575 page.open(url, function(status) {
2576 // use entropy
2577 page.evaluate(function() {
2578 $(".use-entropy").prop("checked", true).trigger("change");
2579 $(".entropy").val("123456").trigger("input");
2580 });
2581 // check the entropy is shown to be the correct type
2582 waitForEntropyFeedback(function() {
2583 var entropyText = page.evaluate(function() {
2584 return $(".entropy-container").text();
2585 });
2586 if (entropyText.indexOf("dice") == -1) {
2587 console.log("Dice entropy is not detected and presented to user");
2588 fail();
2589 }
2590 next();
2591 });
2592 });
2593 },
2594
2595 // Base 10 entropy can be entered
2596 function() {
2597 page.open(url, function(status) {
2598 // use entropy
2599 page.evaluate(function() {
2600 $(".use-entropy").prop("checked", true).trigger("change");
2601 $(".entropy").val("789").trigger("input");
2602 });
2603 // check the entropy is shown to be the correct type
2604 waitForEntropyFeedback(function() {
2605 var entropyText = page.evaluate(function() {
2606 return $(".entropy-container").text();
2607 });
2608 if (entropyText.indexOf("base 10") == -1) {
2609 console.log("Base 10 entropy is not detected and presented to user");
2610 fail();
2611 }
2612 next();
2613 });
2614 });
2615 },
2616
2617 // Hexadecimal entropy can be entered
2618 function() {
2619 page.open(url, function(status) {
2620 // use entropy
2621 page.evaluate(function() {
2622 $(".use-entropy").prop("checked", true).trigger("change");
2623 $(".entropy").val("abcdef").trigger("input");
2624 });
2625 // check the entropy is shown to be the correct type
2626 waitForEntropyFeedback(function() {
2627 var entropyText = page.evaluate(function() {
2628 return $(".entropy-container").text();
2629 });
2630 if (entropyText.indexOf("hexadecimal") == -1) {
2631 console.log("Hexadecimal entropy is not detected and presented to user");
2632 fail();
2633 }
2634 next();
2635 });
2636 });
2637 },
2638
2639 // Dice entropy value is shown as the converted base 6 value
2640 function() {
2641 page.open(url, function(status) {
2642 // use entropy
2643 page.evaluate(function() {
2644 $(".use-entropy").prop("checked", true).trigger("change");
2645 $(".entropy").val("123456").trigger("input");
2646 });
2647 // check the entropy is shown as base 6, not as the original dice value
2648 waitForEntropyFeedback(function() {
2649 var entropyText = page.evaluate(function() {
2650 return $(".entropy-container").text();
2651 });
2652 if (entropyText.indexOf("123450") == -1) {
2653 console.log("Dice entropy is not shown to user as base 6 value");
2654 fail();
2655 }
2656 if (entropyText.indexOf("123456") > -1) {
2657 console.log("Dice entropy value is shown instead of true base 6 value");
2658 fail();
2659 }
2660 next();
2661 });
2662 });
2663 },
2664
2665 // The number of bits of entropy accumulated is shown
2666 function() {
2667 page.open(url, function(status) {
2668 //[ entropy, bits ]
2669 var tests = [
2670 [ "0000 0000 0000 0000 0000", "20" ],
2671 [ "0", "1" ],
2672 [ "0000", "4" ],
2673 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2674 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2675 [ "8", "4" ],
2676 [ "F", "4" ],
2677 [ "29", "6" ],
2678 [ "0A", "8" ],
2679 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2680 [ "2A", "8" ],
2681 [ "4A", "8" ],
2682 [ "8A", "8" ],
2683 [ "FA", "8" ],
2684 [ "000A", "16" ],
2685 [ "5555", "11" ],
2686 [ "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)
2687 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2688 [ "222F", "16" ],
2689 [ "FFFF", "16" ],
2690 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2691 [ "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
2692 ]
2693 // use entropy
2694 page.evaluate(function(e) {
2695 $(".use-entropy").prop("checked", true).trigger("change");
2696 });
2697 // Run each test
2698 var nextTest = function runNextTest(i) {
2699 var entropy = tests[i][0];
2700 var expected = tests[i][1];
2701 // set entropy
2702 page.evaluate(function(e) {
2703 $(".entropy").val(e).trigger("input");
2704 }, entropy);
2705 // check the number of bits of entropy is shown
2706 waitForEntropyFeedback(function() {
2707 var entropyText = page.evaluate(function() {
2708 return $(".entropy-container").text();
2709 });
2710 if (entropyText.replace(/\s/g,"").indexOf("Bits" + expected) == -1) {
2711 console.log("Accumulated entropy is not shown correctly for " + entropy);
2712 fail();
2713 }
2714 var isLastTest = i == tests.length - 1;
2715 if (isLastTest) {
2716 next();
2717 }
2718 else {
2719 runNextTest(i+1);
2720 }
2721 });
2722 }
2723 nextTest(0);
2724 });
2725 },
2726
2727 // There is feedback provided about the supplied entropy
2728 function() {
2729 page.open(url, function(status) {
2730 var tests = [
2731 {
2732 entropy: "A",
2733 filtered: "A",
2734 type: "hexadecimal",
2735 events: 1,
2736 bits: 4,
2737 words: 0,
2738 strength: "extremely weak",
2739 },
2740 {
2741 entropy: "AAAAAAAA",
2742 filtered: "AAAAAAAA",
2743 type: "hexadecimal",
2744 events: 8,
2745 bits: 32,
2746 words: 3,
2747 strength: "extremely weak",
2748 },
2749 {
2750 entropy: "AAAAAAAA B",
2751 filtered: "AAAAAAAAB",
2752 type: "hexadecimal",
2753 events: 9,
2754 bits: 36,
2755 words: 3,
2756 strength: "extremely weak",
2757 },
2758 {
2759 entropy: "AAAAAAAA BBBBBBBB",
2760 filtered: "AAAAAAAABBBBBBBB",
2761 type: "hexadecimal",
2762 events: 16,
2763 bits: 64,
2764 words: 6,
2765 strength: "very weak",
2766 },
2767 {
2768 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2769 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2770 type: "hexadecimal",
2771 events: 24,
2772 bits: 96,
2773 words: 9,
2774 strength: "weak",
2775 },
2776 {
2777 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2778 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2779 type: "hexadecimal",
2780 events: 32,
2781 bits: 128,
2782 words: 12,
2783 strength: "easily cracked",
2784 },
2785 {
2786 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2787 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2788 type: "hexadecimal",
2789 events: 32,
2790 bits: 128,
2791 words: 12,
2792 strength: "strong",
2793 },
2794 {
2795 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2796 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2797 type: "hexadecimal",
2798 events: 40,
2799 bits: 160,
2800 words: 15,
2801 strength: "very strong",
2802 },
2803 {
2804 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2805 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2806 type: "hexadecimal",
2807 events: 48,
2808 bits: 192,
2809 words: 18,
2810 strength: "extremely strong",
2811 },
2812 {
2813 entropy: "7d",
2814 type: "card",
2815 events: 1,
2816 bits: 5,
2817 words: 0,
2818 strength: "extremely weak",
2819 },
2820 {
2821 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2822 type: "card (full deck)",
2823 events: 52,
2824 bits: 225,
2825 words: 21,
2826 strength: "extremely strong",
2827 },
2828 {
2829 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2830 type: "card (full deck, 1 duplicate: 3d)",
2831 events: 53,
2832 bits: 254,
2833 words: 21,
2834 strength: "extremely strong",
2835 },
2836 {
2837 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2838 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2839 events: 53,
2840 bits: 254,
2841 words: 21,
2842 strength: "extremely strong",
2843 },
2844 {
2845 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2846 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2847 events: 53,
2848 bits: 264,
2849 words: 24,
2850 strength: "extremely strong",
2851 },
2852 // Next test was throwing uncaught error in zxcvbn
2853 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2854 {
2855 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2856 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2857 events: 104,
2858 bits: 499,
2859 words: 45,
2860 strength: "extremely strong",
2861 },
2862 // Case insensitivity to duplicate cards
2863 {
2864 entropy: "asAS",
2865 type: "card (1 duplicate: AS)",
2866 events: 2,
2867 bits: 9,
2868 words: 0,
2869 strength: "extremely weak",
2870 },
2871 {
2872 entropy: "ASas",
2873 type: "card (1 duplicate: as)",
2874 events: 2,
2875 bits: 9,
2876 words: 0,
2877 strength: "extremely weak",
2878 },
2879 // Missing cards are detected
2880 {
2881 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2882 type: "card (1 missing: 9C)",
2883 events: 51,
2884 bits: 221,
2885 words: 18,
2886 strength: "extremely strong",
2887 },
2888 {
2889 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2890 type: "card (2 missing: 9C 5D)",
2891 events: 50,
2892 bits: 216,
2893 words: 18,
2894 strength: "extremely strong",
2895 },
2896 {
2897 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2898 type: "card (4 missing: 9C 5D QD...)",
2899 events: 48,
2900 bits: 208,
2901 words: 18,
2902 strength: "extremely strong",
2903 },
2904 // More than six missing cards does not show message
2905 {
2906 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2907 type: "card",
2908 events: 45,
2909 bits: 195,
2910 words: 18,
2911 strength: "extremely strong",
2912 },
2913 // Multiple decks of cards increases bits per event
2914 {
2915 entropy: "3d",
2916 events: 1,
2917 bits: 4,
2918 bitsPerEvent: 4.34,
2919 },
2920 {
2921 entropy: "3d3d",
2922 events: 2,
2923 bits: 9,
2924 bitsPerEvent: 4.80,
2925 },
2926 {
2927 entropy: "3d3d3d",
2928 events: 3,
2929 bits: 15,
2930 bitsPerEvent: 5.01,
2931 },
2932 {
2933 entropy: "3d3d3d3d",
2934 events: 4,
2935 bits: 20,
2936 bitsPerEvent: 5.14,
2937 },
2938 {
2939 entropy: "3d3d3d3d3d",
2940 events: 5,
2941 bits: 26,
2942 bitsPerEvent: 5.22,
2943 },
2944 {
2945 entropy: "3d3d3d3d3d3d",
2946 events: 6,
2947 bits: 31,
2948 bitsPerEvent: 5.28,
2949 },
2950 {
2951 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2952 events: 33,
2953 bits: 184,
2954 bitsPerEvent: 5.59,
2955 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2956 },
2957 ];
2958 // use entropy
2959 page.evaluate(function() {
2960 $(".use-entropy").prop("checked", true).trigger("change");
2961 });
2962 var nextTest = function runNextTest(i) {
2963 function getFeedbackError(expected, actual) {
2964 if ("filtered" in expected && actual.indexOf(expected.filtered) == -1) {
2965 return "Filtered value not in feedback";
2966 }
2967 if ("type" in expected && actual.indexOf(expected.type) == -1) {
2968 return "Entropy type not in feedback";
2969 }
2970 if ("events" in expected && actual.indexOf(expected.events) == -1) {
2971 return "Event count not in feedback";
2972 }
2973 if ("bits" in expected && actual.indexOf(expected.bits) == -1) {
2974 return "Bit count not in feedback";
2975 }
2976 if ("strength" in expected && actual.indexOf(expected.strength) == -1) {
2977 return "Strength not in feedback";
2978 }
2979 if ("bitsPerEvent" in expected && actual.indexOf(expected.bitsPerEvent) == -1) {
2980 return "bitsPerEvent not in feedback";
2981 }
2982 return false;
2983 }
2984 test = tests[i];
2985 page.evaluate(function(e) {
2986 $(".addresses").empty();
2987 $(".phrase").val("");
2988 $(".entropy").val(e).trigger("input");
2989 }, test.entropy);
2990 waitForEntropyFeedback(function() {
2991 var mnemonic = page.evaluate(function() {
2992 return $(".phrase").val();
2993 });
2994 // Check mnemonic length
2995 if ("words" in test && test.words == 0) {
2996 if (mnemonic.length > 0) {
2997 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2998 console.log("Entropy: " + test.entropy);
2999 console.log("Mnemonic: " + mnemonic);
3000 fail();
3001 }
3002 }
3003 else if ("words" in test) {
3004 if (mnemonic.split(" ").length != test.words) {
3005 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
3006 console.log("Entropy: " + test.entropy);
3007 console.log("Mnemonic: " + mnemonic);
3008 fail();
3009 }
3010 }
3011 // check feedback
3012 var feedback = page.evaluate(function() {
3013 return $(".entropy-container").text();
3014 });
3015 var feedbackError = getFeedbackError(test, feedback);
3016 if (feedbackError) {
3017 console.log("Entropy feedback for " + test.entropy + " returned error");
3018 console.log(feedbackError);
3019 fail();
3020 }
3021 // Run next test
3022 var isLastTest = i == tests.length - 1;
3023 if (isLastTest) {
3024 next();
3025 }
3026 else {
3027 runNextTest(i+1);
3028 }
3029 });
3030 }
3031 nextTest(0);
3032 });
3033 },
3034
3035 // Entropy is truncated from the left
3036 function() {
3037 page.open(url, function(status) {
3038 var expected = "avocado zoo zone";
3039 // use entropy
3040 page.evaluate(function() {
3041 $(".use-entropy").prop("checked", true).trigger("change");
3042 var entropy = "00000000 00000000 00000000 00000000";
3043 entropy += "11111111 11111111 11111111 1111"; // Missing last byte
3044 $(".entropy").val(entropy).trigger("input");
3045 });
3046 // check the entropy is truncated from the right
3047 waitForGenerate(function() {
3048 var actual = page.evaluate(function() {
3049 return $(".phrase").val();
3050 });
3051 if (actual != expected) {
3052 console.log("Entropy is not truncated from the right");
3053 console.log("Expected: " + expected);
3054 console.log("Got: " + actual);
3055 fail();
3056 }
3057 next();
3058 });
3059 });
3060 },
3061
3062 // Very large entropy results in very long mnemonics
3063 function() {
3064 page.open(url, function(status) {
3065 // use entropy
3066 page.evaluate(function() {
3067 $(".use-entropy").prop("checked", true).trigger("change");
3068 var entropy = "";
3069 // Generate a very long entropy string
3070 for (var i=0; i<33; i++) {
3071 entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words
3072 }
3073 $(".entropy").val(entropy).trigger("input");
3074 });
3075 // check the mnemonic is very long
3076 waitForGenerate(function() {
3077 var wordCount = page.evaluate(function() {
3078 return $(".phrase").val().split(" ").length;
3079 });
3080 if (wordCount != 99) {
3081 console.log("Large entropy does not generate long mnemonic");
3082 console.log("Expected 99 words, got " + wordCount);
3083 fail();
3084 }
3085 next();
3086 });
3087 });
3088 },
3089
3090 // Is compatible with bip32jp entropy
3091 // https://bip32jp.github.io/english/index.html
3092 // NOTES:
3093 // Is incompatible with:
3094 // base 20
3095 function() {
3096 page.open(url, function(status) {
3097 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";
3098 // use entropy
3099 page.evaluate(function() {
3100 $(".use-entropy").prop("checked", true).trigger("change");
3101 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
3102 $(".entropy").val(entropy).trigger("input");
3103 });
3104 // check the mnemonic matches the expected value from bip32jp
3105 waitForGenerate(function() {
3106 var actual = page.evaluate(function() {
3107 return $(".phrase").val();
3108 });
3109 if (actual != expected) {
3110 console.log("Mnemonic does not match bip32jp for base 6 entropy");
3111 console.log("Expected: " + expected);
3112 console.log("Got: " + actual);
3113 fail();
3114 }
3115 next();
3116 });
3117 });
3118 },
3119
3120 // Blank entropy does not generate mnemonic or addresses
3121 function() {
3122 page.open(url, function(status) {
3123 // use entropy
3124 page.evaluate(function() {
3125 $(".use-entropy").prop("checked", true).trigger("change");
3126 $(".entropy").val("").trigger("input");
3127 });
3128 waitForFeedback(function() {
3129 // check there is no mnemonic
3130 var phrase = page.evaluate(function() {
3131 return $(".phrase").val();
3132 });
3133 if (phrase != "") {
3134 console.log("Blank entropy does not result in blank mnemonic");
3135 console.log("Got: " + phrase);
3136 fail();
3137 }
3138 // check there are no addresses displayed
3139 var addresses = page.evaluate(function() {
3140 return $(".address").length;
3141 });
3142 if (addresses != 0) {
3143 console.log("Blank entropy does not result in zero addresses");
3144 fail();
3145 }
3146 // Check the feedback says 'blank entropy'
3147 var feedback = page.evaluate(function() {
3148 return $(".feedback").text();
3149 });
3150 if (feedback != "Blank entropy") {
3151 console.log("Blank entropy does not show feedback message");
3152 fail();
3153 }
3154 next();
3155 });
3156 });
3157 },
3158
3159 // Mnemonic length can be selected even for weak entropy
3160 function() {
3161 page.open(url, function(status) {
3162 // use entropy
3163 page.evaluate(function() {
3164 $(".use-entropy").prop("checked", true).trigger("change");
3165 $(".entropy").val("012345");
3166 $(".mnemonic-length").val("18").trigger("change");
3167 });
3168 // check the mnemonic is the correct length
3169 waitForGenerate(function() {
3170 var phrase = page.evaluate(function() {
3171 return $(".phrase").val();
3172 });
3173 var numberOfWords = phrase.split(/\s/g).length;
3174 if (numberOfWords != 18) {
3175 console.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3176 console.log(phrase);
3177 fail();
3178 }
3179 next();
3180 });
3181 });
3182 },
3183
3184 // Github issue 33
3185 // https://github.com/iancoleman/bip39/issues/33
3186 // Final cards should contribute entropy
3187 function() {
3188 page.open(url, function(status) {
3189 // use entropy
3190 page.evaluate(function() {
3191 $(".use-entropy").prop("checked", true).trigger("change");
3192 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-6C TS TH 4H KC 5H 2H AH 2C 8D 3H 5D").trigger("input");
3193 });
3194 // get the mnemonic
3195 waitForGenerate(function() {
3196 var originalPhrase = page.evaluate(function() {
3197 return $(".phrase").val();
3198 });
3199 // Set the last 12 cards to be AS
3200 page.evaluate(function() {
3201 $(".addresses").empty();
3202 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-AS AS AS AS AS AS AS AS AS AS AS AS").trigger("input");
3203 });
3204 // get the new mnemonic
3205 waitForGenerate(function() {
3206 var newPhrase = page.evaluate(function() {
3207 return $(".phrase").val();
3208 });
3209 // check the phrase has changed
3210 if (newPhrase == originalPhrase) {
3211 console.log("Changing last 12 cards does not change mnemonic");
3212 console.log("Original:");
3213 console.log(originalPhrase);
3214 console.log("New:");
3215 console.log(newPhrase);
3216 fail();
3217 }
3218 next();
3219 });
3220 });
3221 });
3222 },
3223
3224 // Github issue 35
3225 // https://github.com/iancoleman/bip39/issues/35
3226 // QR Code support
3227 function() {
3228 page.open(url, function(status) {
3229 // use entropy
3230 page.evaluate(function() {
3231 $(".generate").click();
3232 });
3233 waitForGenerate(function() {
3234 var p = page.evaluate(function() {
3235 // get position of mnemonic element
3236 return $(".phrase").offset();
3237 });
3238 p.top = Math.ceil(p.top);
3239 p.left = Math.ceil(p.left);
3240 // check the qr code shows
3241 page.sendEvent("mousemove", p.left+4, p.top+4);
3242 var qrShowing = page.evaluate(function() {
3243 return $(".qr-container").find("canvas").length > 0;
3244 });
3245 if (!qrShowing) {
3246 console.log("QR Code does not show");
3247 fail();
3248 }
3249 // check the qr code hides
3250 page.sendEvent("mousemove", p.left-4, p.top-4);
3251 var qrHidden = page.evaluate(function() {
3252 return $(".qr-container").find("canvas").length == 0;
3253 });
3254 if (!qrHidden) {
3255 console.log("QR Code does not hide");
3256 fail();
3257 }
3258 next();
3259 });
3260 });
3261 },
3262
3263 // BIP44 account extendend private key is shown
3264 // github issue 37 - compatibility with electrum
3265 function() {
3266 page.open(url, function(status) {
3267 // set the phrase
3268 var expected = "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3269 page.evaluate(function() {
3270 $(".phrase").val("abandon abandon ability");
3271 $(".phrase").trigger("input");
3272 });
3273 // check the BIP44 account extended private key
3274 waitForGenerate(function() {
3275 var actual = page.evaluate(function() {
3276 return $(".account-xprv").val();
3277 });
3278 if (actual != expected) {
3279 console.log("BIP44 account extended private key is incorrect");
3280 console.log("Expected: " + expected);
3281 console.log("Actual: " + actual);
3282 fail();
3283 }
3284 next();
3285 });
3286 });
3287 },
3288
3289 // BIP44 account extendend public key is shown
3290 // github issue 37 - compatibility with electrum
3291 function() {
3292 page.open(url, function(status) {
3293 // set the phrase
3294 var expected = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3295 page.evaluate(function() {
3296 $(".phrase").val("abandon abandon ability");
3297 $(".phrase").trigger("input");
3298 });
3299 // check the BIP44 account extended public key
3300 waitForGenerate(function() {
3301 var actual = page.evaluate(function() {
3302 return $(".account-xpub").val();
3303 });
3304 if (actual != expected) {
3305 console.log("BIP44 account extended public key is incorrect");
3306 console.log("Expected: " + expected);
3307 console.log("Actual: " + actual);
3308 fail();
3309 }
3310 next();
3311 });
3312 });
3313 },
3314
3315 // github issue 40
3316 // BIP32 root key can be set as an xpub
3317 function() {
3318 page.open(url, function(status) {
3319 // set the phrase
3320 page.evaluate(function() {
3321 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3322 var bip44AccountXpub = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3323 $("#root-key").val(bip44AccountXpub);
3324 $("#root-key").trigger("input");
3325 });
3326 waitForFeedback(function() {
3327 page.evaluate(function() {
3328 // Use bip32 tab
3329 $("#bip32-tab a").click();
3330 });
3331 waitForGenerate(function() {
3332 page.evaluate(function() {
3333 // derive external addresses for this xpub
3334 var firstAccountDerivationPath = "m/0";
3335 $("#bip32-path").val(firstAccountDerivationPath);
3336 $("#bip32-path").trigger("input");
3337 });
3338 waitForGenerate(function() {
3339 // check the addresses are generated
3340 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3341 var actual = page.evaluate(function() {
3342 return $(".address:first").text();
3343 });
3344 if (actual != expected) {
3345 console.log("xpub key does not generate addresses in table");
3346 console.log("Expected: " + expected);
3347 console.log("Actual: " + actual);
3348 fail();
3349 }
3350 // check the xprv key is not set
3351 var expected = "NA";
3352 var actual = page.evaluate(function() {
3353 return $(".extended-priv-key").val();
3354 });
3355 if (actual != expected) {
3356 console.log("xpub key as root shows derived bip32 xprv key");
3357 console.log("Expected: " + expected);
3358 console.log("Actual: " + actual);
3359 fail();
3360 }
3361 // check the private key is not set
3362 var expected = "NA";
3363 var actual = page.evaluate(function() {
3364 return $(".privkey:first").text();
3365 });
3366 if (actual != expected) {
3367 console.log("xpub key generates private key in addresses table");
3368 console.log("Expected: " + expected);
3369 console.log("Actual: " + actual);
3370 fail();
3371 }
3372 next();
3373 });
3374 });
3375 });
3376 });
3377 },
3378
3379 // github issue 40
3380 // xpub for bip32 root key will not work with hardened derivation paths
3381 function() {
3382 page.open(url, function(status) {
3383 // set the phrase
3384 page.evaluate(function() {
3385 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3386 var bip44AccountXpub = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3387 $("#root-key").val(bip44AccountXpub);
3388 $("#root-key").trigger("input");
3389 });
3390 waitForFeedback(function() {
3391 // Check feedback is correct
3392 var expected = "Hardened derivation path is invalid with xpub key";
3393 var actual = page.evaluate(function() {
3394 return $(".feedback").text();
3395 });
3396 if (actual != expected) {
3397 console.log("xpub key with hardened derivation path does not show feedback");
3398 console.log("Expected: " + expected);
3399 console.log("Actual: " + actual);
3400 fail();
3401 }
3402 // Check no addresses are shown
3403 var expected = 0;
3404 var actual = page.evaluate(function() {
3405 return $(".addresses tr").length;
3406 });
3407 if (actual != expected) {
3408 console.log("addresses still show after setting xpub key with hardened derivation path");
3409 console.log("Expected: " + expected);
3410 console.log("Actual: " + actual);
3411 fail();
3412 }
3413 next();
3414 });
3415 });
3416 },
3417
3418 // github issue 39
3419 // no root key shows feedback
3420 function() {
3421 page.open(url, function(status) {
3422 // click the bip32 tab on fresh page
3423 page.evaluate(function() {
3424 $("#bip32-tab a").click();
3425 });
3426 waitForFeedback(function() {
3427 // Check feedback is correct
3428 var expected = "No root key";
3429 var actual = page.evaluate(function() {
3430 return $(".feedback").text();
3431 });
3432 if (actual != expected) {
3433 console.log("Blank root key not detected");
3434 console.log("Expected: " + expected);
3435 console.log("Actual: " + actual);
3436 fail();
3437 }
3438 next();
3439 });
3440 });
3441 },
3442
3443 // Github issue 44
3444 // display error switching tabs while addresses are generating
3445 function() {
3446 page.open(url, function(status) {
3447 // set the phrase
3448 page.evaluate(function() {
3449 $(".phrase").val("abandon abandon ability").trigger("input");
3450 });
3451 waitForGenerate(function() {
3452 // set to generate 500 more addresses
3453 // generate more addresses
3454 // change tabs which should cancel the previous generating
3455 page.evaluate(function() {
3456 $(".rows-to-add").val("100");
3457 $(".more").click();
3458 $("#bip32-tab a").click();
3459 });
3460 // check the derivation paths are in order and of the right quantity
3461 waitForGenerate(function() {
3462 var paths = page.evaluate(function() {
3463 return $(".index").map(function(i, e) {
3464 return $(e).text();
3465 });
3466 });
3467 for (var i=0; i<paths.length; i++) {
3468 var expected = "m/0/" + i;
3469 var actual = paths[i];
3470 if (actual != expected) {
3471 console.log("Path " + i + " is not in correct order");
3472 console.log("Expected: " + expected);
3473 console.log("Actual: " + actual);
3474 fail();
3475 }
3476 }
3477 if (paths.length != 20) {
3478 console.log("Generation was not cancelled by new action");
3479 fail();
3480 }
3481 next();
3482 });
3483 });
3484 });
3485 },
3486
3487 // Github issue 49
3488 // padding for binary should give length with multiple of 256
3489 // hashed entropy 1111 is length 252, so requires 4 leading zeros
3490 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
3491 function() {
3492 page.open(url, function(status) {
3493 expected = "avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear"
3494 // use entropy
3495 page.evaluate(function() {
3496 $(".use-entropy").prop("checked", true).trigger("change");
3497 $(".mnemonic-length").val("15");
3498 $(".entropy").val("1111").trigger("input");
3499 });
3500 waitForGenerate(function() {
3501 // get the mnemonic
3502 var actual = page.evaluate(function() {
3503 return $(".phrase").val();
3504 });
3505 // check the mnemonic is correct
3506 if (actual != expected) {
3507 console.log("Left padding error for entropy");
3508 console.log("Expected: " + expected);
3509 console.log("Actual: " + actual);
3510 fail();
3511 }
3512 next();
3513 });
3514 });
3515 },
3516
3517 // Github pull request 55
3518 // https://github.com/iancoleman/bip39/pull/55
3519 // Client select
3520 function() {
3521 page.open(url, function(status) {
3522 // set mnemonic and select bip32 tab
3523 page.evaluate(function() {
3524 $("#bip32-tab a").click();
3525 $(".phrase").val("abandon abandon ability").trigger("input");
3526 });
3527 waitForGenerate(function() {
3528 // BITCOIN CORE
3529 // set bip32 client to bitcoin core
3530 page.evaluate(function() {
3531 var bitcoinCoreIndex = "0";
3532 $("#bip32-client").val(bitcoinCoreIndex).trigger("change");
3533 });
3534 waitForGenerate(function() {
3535 // get the derivation path
3536 var actual = page.evaluate(function() {
3537 return $("#bip32-path").val();
3538 });
3539 // check the derivation path is correct
3540 expected = "m/0'/0'"
3541 if (actual != expected) {
3542 console.log("Selecting Bitcoin Core client does not set correct derivation path");
3543 console.log("Expected: " + expected);
3544 console.log("Actual: " + actual);
3545 fail();
3546 }
3547 // get hardened addresses
3548 var usesHardenedAddresses = page.evaluate(function() {
3549 return $(".hardened-addresses").prop("checked");
3550 });
3551 // check hardened addresses is selected
3552 if(!usesHardenedAddresses) {
3553 console.log("Selecting Bitcoin Core client does not use hardened addresses");
3554 fail();
3555 }
3556 // check input is readonly
3557 var pathIsReadonly = page.evaluate(function() {
3558 return $("#bip32-path").prop("readonly");
3559 });
3560 if (!pathIsReadonly) {
3561 console.log("Selecting Bitcoin Core client does not set derivation path to readonly");
3562 fail();
3563 }
3564 // MULTIBIT
3565 // set bip32 client to multibit
3566 page.evaluate(function() {
3567 var multibitIndex = "2";
3568 $("#bip32-client").val(multibitIndex).trigger("change");
3569 });
3570 waitForGenerate(function() {
3571 // get the derivation path
3572 var actual = page.evaluate(function() {
3573 return $("#bip32-path").val();
3574 });
3575 // check the derivation path is correct
3576 expected = "m/0'/0"
3577 if (actual != expected) {
3578 console.log("Selecting Multibit client does not set correct derivation path");
3579 console.log("Expected: " + expected);
3580 console.log("Actual: " + actual);
3581 fail();
3582 }
3583 // get hardened addresses
3584 var usesHardenedAddresses = page.evaluate(function() {
3585 return $(".hardened-addresses").prop("checked");
3586 });
3587 // check hardened addresses is selected
3588 if(usesHardenedAddresses) {
3589 console.log("Selecting Multibit client does not uncheck hardened addresses");
3590 fail();
3591 }
3592 // CUSTOM DERIVATION PATH
3593 // check input is not readonly
3594 page.evaluate(function() {
3595 $("#bip32-client").val("custom").trigger("change");
3596 });
3597 // do not wait for generate, since there is no change to the
3598 // derivation path there is no new generation performed
3599 var pathIsReadonly = page.evaluate(function() {
3600 return $("#bip32-path").prop("readonly");
3601 });
3602 if (pathIsReadonly) {
3603 console.log("Selecting Custom Derivation Path does not allow derivation path input");
3604 fail();
3605 }
3606 next();
3607 });
3608 });
3609 });
3610 });
3611 },
3612
3613 // github issue 58
3614 // https://github.com/iancoleman/bip39/issues/58
3615 // bip32 derivation is correct, does not drop leading zeros
3616 // see also
3617 // https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846
3618 function() {
3619 page.open(url, function(status) {
3620 // set the phrase and passphrase
3621 var expected = "17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F";
3622 // Note that bitcore generates an incorrect address
3623 // 13EuKhffWkBE2KUwcbkbELZb1MpzbimJ3Y
3624 // see the medium.com link above for more details
3625 page.evaluate(function() {
3626 $(".phrase").val("fruit wave dwarf banana earth journey tattoo true farm silk olive fence");
3627 $(".passphrase").val("banana").trigger("input");
3628 });
3629 // check the address is generated correctly
3630 waitForGenerate(function() {
3631 var actual = page.evaluate(function() {
3632 return $(".address:first").text();
3633 });
3634 if (actual != expected) {
3635 console.log("BIP32 derivation is incorrect");
3636 console.log("Expected: " + expected);
3637 console.log("Actual: " + actual);
3638 fail();
3639 }
3640 next();
3641 });
3642 });
3643 },
3644
3645
3646 // github issue 60
3647 // Japanese mnemonics generate incorrect bip32 seed
3648 // BIP39 seed is set from phrase
3649 function() {
3650 page.open(url, function(status) {
3651 // set the phrase
3652 var expected = "a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55";
3653 page.evaluate(function() {
3654 $(".phrase").val("あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら");
3655 $("#passphrase").val("メートルガバヴァぱばぐゞちぢ十人十色");
3656 $("#passphrase").trigger("input");
3657 });
3658 // check the seed is generated correctly
3659 waitForGenerate(function() {
3660 var actual = page.evaluate(function() {
3661 return $(".seed").val();
3662 });
3663 if (actual != expected) {
3664 console.log("BIP39 seed is incorrectly generated from Japanese mnemonic");
3665 console.log("Expected: " + expected);
3666 console.log("Actual: " + actual);
3667 fail();
3668 }
3669 next();
3670 });
3671 });
3672 },
3673
3674 // If you wish to add more tests, do so here...
3675
3676 // Here is a blank test template
3677 /*
3678
3679 function() {
3680 page.open(url, function(status) {
3681 // Do something on the page
3682 page.evaluate(function() {
3683 $(".phrase").val("abandon abandon ability").trigger("input");
3684 });
3685 waitForGenerate(function() {
3686 // Check the result of doing the thing
3687 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3688 var actual = page.evaluate(function() {
3689 return $(".address:first").text();
3690 });
3691 if (actual != expected) {
3692 console.log("A specific message about what failed");
3693 console.log("Expected: " + expected);
3694 console.log("Actual: " + actual);
3695 fail();
3696 }
3697 // Run the next test
3698 next();
3699 });
3700 });
3701 },
3702
3703 */
3704
3705 ];
3706
3707 console.log("Running tests...");
3708 tests = shuffle(tests);
3709 next();