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