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