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