]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
xpub key can be used as bip32 root key
[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 // Incorrect BIP32 root key shows error
1566 function() {
1567 page.open(url, function(status) {
1568 // set the root key
1569 page.evaluate(function() {
1570 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1571 });
1572 // check there is an error shown
1573 waitForFeedback(function() {
1574 var feedback = page.evaluate(function() {
1575 return $(".feedback").text();
1576 });
1577 if (feedback != "Invalid root key") {
1578 console.log("Invalid root key does not show error");
1579 console.log("Error: " + error);
1580 fail();
1581 }
1582 next();
1583 });
1584 });
1585 },
1586
1587 // Derivation path not starting with m shows error
1588 function() {
1589 page.open(url, function(status) {
1590 // set the mnemonic phrase
1591 page.evaluate(function() {
1592 $(".phrase").val("abandon abandon ability").trigger("input");
1593 });
1594 waitForGenerate(function() {
1595 // select the bip32 tab so custom derivation path can be set
1596 page.evaluate(function() {
1597 $("#bip32-tab a").click();
1598 });
1599 waitForGenerate(function() {
1600 // set the incorrect derivation path
1601 page.evaluate(function() {
1602 $("#bip32 .path").val("n/0").trigger("input");
1603 });
1604 waitForFeedback(function() {
1605 var feedback = page.evaluate(function() {
1606 return $(".feedback").text();
1607 });
1608 if (feedback != "First character must be 'm'") {
1609 console.log("Derivation path not starting with m should show error");
1610 console.log("Error: " + error);
1611 fail();
1612 }
1613 next();
1614 });
1615 });
1616 });
1617 });
1618 },
1619
1620 // Derivation path containing invalid characters shows useful error
1621 function() {
1622 page.open(url, function(status) {
1623 // set the mnemonic phrase
1624 page.evaluate(function() {
1625 $(".phrase").val("abandon abandon ability").trigger("input");
1626 });
1627 waitForGenerate(function() {
1628 // select the bip32 tab so custom derivation path can be set
1629 page.evaluate(function() {
1630 $("#bip32-tab a").click();
1631 });
1632 waitForGenerate(function() {
1633 // set the incorrect derivation path
1634 page.evaluate(function() {
1635 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1636 });
1637 waitForFeedback(function() {
1638 var feedback = page.evaluate(function() {
1639 return $(".feedback").text();
1640 });
1641 if (feedback != "Invalid characters 0wrong1 found at depth 2") {
1642 console.log("Derivation path with invalid characters should show error");
1643 console.log("Error: " + error);
1644 fail();
1645 }
1646 next();
1647 });
1648 });
1649 });
1650 });
1651 },
1652
1653 // Github Issue 11: Default word length is 15
1654 // https://github.com/iancoleman/bip39/issues/11
1655 function() {
1656 page.open(url, function(status) {
1657 // get the word length
1658 var defaultLength = page.evaluate(function() {
1659 return $(".strength").val();
1660 });
1661 if (defaultLength != 15) {
1662 console.log("Default word length is not 15");
1663 fail();
1664 }
1665 next();
1666 });
1667 },
1668
1669
1670 // Github Issue 12: Generate more rows with private keys hidden
1671 // https://github.com/iancoleman/bip39/issues/12
1672 function() {
1673 page.open(url, function(status) {
1674 // set the phrase
1675 page.evaluate(function() {
1676 $(".phrase").val("abandon abandon ability");
1677 $(".phrase").trigger("input");
1678 });
1679 waitForGenerate(function() {
1680 // toggle private keys hidden, then generate more addresses
1681 page.evaluate(function() {
1682 $(".private-key-toggle").click();
1683 $(".more").click();
1684 });
1685 waitForGenerate(function() {
1686 // check more have been generated
1687 var expected = 40;
1688 var numPrivKeys = page.evaluate(function() {
1689 return $(".privkey").length;
1690 });
1691 if (numPrivKeys != expected) {
1692 console.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1693 console.log("Expected: " + expected);
1694 console.log("Actual: " + numPrivKeys);
1695 fail();
1696 }
1697 // check no private keys are shown
1698 var numHiddenPrivKeys = page.evaluate(function() {
1699 return $(".privkey span[class=invisible]").length;
1700 });
1701 if (numHiddenPrivKeys != expected) {
1702 console.log("Generating more does not retain hidden state of privkeys");
1703 console.log("Expected: " + expected);
1704 console.log("Actual: " + numHiddenPrivKeys);
1705 fail();
1706 }
1707 next();
1708 });
1709 });
1710 });
1711 },
1712
1713 // Github Issue 19: Mnemonic is not sensitive to whitespace
1714 // https://github.com/iancoleman/bip39/issues/19
1715 function() {
1716 page.open(url, function(status) {
1717 // set the phrase
1718 var expected = "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1719 page.evaluate(function() {
1720 var doubleSpace = " ";
1721 $(".phrase").val("urge cat" + doubleSpace + "bid");
1722 $(".phrase").trigger("input");
1723 });
1724 waitForGenerate(function() {
1725 // Check the bip32 root key is correct
1726 var actual = page.evaluate(function() {
1727 return $(".root-key").val();
1728 });
1729 if (actual != expected) {
1730 console.log("Mnemonic is sensitive to whitespace");
1731 console.log("Expected: " + expected);
1732 console.log("Actual: " + actual);
1733 fail();
1734 }
1735 next();
1736 });
1737 });
1738 },
1739
1740 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1741 // https://github.com/iancoleman/bip39/issues/23
1742 function() {
1743 page.open(url, function(status) {
1744 // 1) and 2) set the phrase
1745 page.evaluate(function() {
1746 $(".phrase").val("abandon abandon ability").trigger("input");
1747 });
1748 waitForGenerate(function() {
1749 // 3) select bip32 tab
1750 page.evaluate(function() {
1751 $("#bip32-tab a").click();
1752 });
1753 waitForGenerate(function() {
1754 // 4) switch from bitcoin to litecoin
1755 page.evaluate(function() {
1756 $(".network").val("2").trigger("change");
1757 });
1758 waitForGenerate(function() {
1759 // 5) Check derivation path is displayed correctly
1760 var expected = "m/0/0";
1761 var actual = page.evaluate(function() {
1762 return $(".index:first").text();
1763 });
1764 if (actual != expected) {
1765 console.log("Github Issue 23 Part 1: derivation path display error");
1766 console.log("Expected: " + expected);
1767 console.log("Actual: " + actual);
1768 fail();
1769 }
1770 // 5) Check address is displayed correctly
1771 var expected = "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1772 var actual = page.evaluate(function() {
1773 return $(".address:first").text();
1774 });
1775 if (actual != expected) {
1776 console.log("Github Issue 23 Part 1: address display error");
1777 console.log("Expected: " + expected);
1778 console.log("Actual: " + actual);
1779 fail();
1780 }
1781 next();
1782 });
1783 });
1784 });
1785 });
1786 },
1787
1788 // Github Issue 23 Part 2: Coin selection in derivation path
1789 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1790 function() {
1791 page.open(url, function(status) {
1792 // set the phrase
1793 page.evaluate(function() {
1794 $(".phrase").val("abandon abandon ability").trigger("input");
1795 });
1796 waitForGenerate(function() {
1797 // switch from bitcoin to clam
1798 page.evaluate(function() {
1799 $(".network").val("9").trigger("change");
1800 });
1801 waitForGenerate(function() {
1802 // check derivation path is displayed correctly
1803 var expected = "m/44'/23'/0'/0/0";
1804 var actual = page.evaluate(function() {
1805 return $(".index:first").text();
1806 });
1807 if (actual != expected) {
1808 console.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1809 console.log("Expected: " + expected);
1810 console.log("Actual: " + actual);
1811 fail();
1812 }
1813 next();
1814 });
1815 });
1816 });
1817 },
1818
1819 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1820 // https://github.com/iancoleman/bip39/issues/26
1821 function() {
1822 page.open(url, function(status) {
1823 // 1) 2) and 3) set the root key
1824 page.evaluate(function() {
1825 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1826 });
1827 waitForGenerate(function() {
1828 // 4) switch from bitcoin to viacoin
1829 page.evaluate(function() {
1830 $(".network").val("6").trigger("change");
1831 });
1832 waitForGenerate(function() {
1833 // 5) ensure the derived address is correct
1834 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1835 var actual = page.evaluate(function() {
1836 return $(".address:first").text();
1837 });
1838 if (actual != expected) {
1839 console.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1840 console.log("Expected: " + expected);
1841 console.log("Actual: " + actual);
1842 fail();
1843 }
1844 next();
1845 });
1846 });
1847 });
1848 },
1849
1850 // Selecting a language with no existing phrase should generate a phrase in
1851 // that language.
1852 function() {
1853 page.open(url, function(status) {
1854 // Select a language
1855 // Need to manually simulate hash being set due to quirk between
1856 // 'click' event triggered by javascript vs triggered by mouse.
1857 // Perhaps look into page.sendEvent
1858 // http://phantomjs.org/api/webpage/method/send-event.html
1859 page.evaluate(function() {
1860 window.location.hash = "#japanese";
1861 $("a[href='#japanese']").trigger("click");
1862 });
1863 waitForGenerate(function() {
1864 // Check the mnemonic is in Japanese
1865 var phrase = page.evaluate(function() {
1866 return $(".phrase").val();
1867 });
1868 if (phrase.length <= 0) {
1869 console.log("No Japanese phrase generated");
1870 fail();
1871 }
1872 if (phrase.charCodeAt(0) < 128) {
1873 console.log("First character of Japanese phrase is ascii");
1874 console.log("Phrase: " + phrase);
1875 fail();
1876 }
1877 next();
1878 });
1879 });
1880 },
1881
1882 // Selecting a language with existing phrase should update the phrase to use
1883 // that language.
1884 function() {
1885 page.open(url, function(status) {
1886 // Set the phrase to an English phrase.
1887 page.evaluate(function() {
1888 $(".phrase").val("abandon abandon ability").trigger("input");
1889 });
1890 waitForGenerate(function() {
1891 // Change to Italian
1892 // Need to manually simulate hash being set due to quirk between
1893 // 'click' event triggered by javascript vs triggered by mouse.
1894 // Perhaps look into page.sendEvent
1895 // http://phantomjs.org/api/webpage/method/send-event.html
1896 page.evaluate(function() {
1897 window.location.hash = "#italian";
1898 $("a[href='#italian']").trigger("click");
1899 });
1900 waitForGenerate(function() {
1901 // Check only the language changes, not the phrase
1902 var expected = "abaco abaco abbaglio";
1903 var actual = page.evaluate(function() {
1904 return $(".phrase").val();
1905 });
1906 if (actual != expected) {
1907 console.log("Changing language with existing phrase");
1908 console.log("Expected: " + expected);
1909 console.log("Actual: " + actual);
1910 fail();
1911 }
1912 // Check the address is correct
1913 var expected = "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1914 var actual = page.evaluate(function() {
1915 return $(".address:first").text();
1916 });
1917 if (actual != expected) {
1918 console.log("Changing language generates incorrect address");
1919 console.log("Expected: " + expected);
1920 console.log("Actual: " + actual);
1921 fail();
1922 }
1923 next();
1924 });
1925 });
1926 });
1927 },
1928
1929 // Suggested replacement for erroneous word in non-English language
1930 function() {
1931 page.open(url, function(status) {
1932 // Set an incorrect phrase in Italian
1933 page.evaluate(function() {
1934 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
1935 });
1936 waitForFeedback(function() {
1937 // Check the suggestion is correct
1938 var feedback = page.evaluate(function() {
1939 return $(".feedback").text();
1940 });
1941 if (feedback.indexOf("did you mean abbaglio?") < 0) {
1942 console.log("Incorrect Italian word does not show suggested replacement");
1943 console.log("Error: " + error);
1944 fail();
1945 }
1946 next();
1947 });
1948 });
1949 },
1950
1951
1952 // Japanese word does not break across lines.
1953 // Point 2 from
1954 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1955 function() {
1956 page.open(url, function(status) {
1957 hasWordBreakCss = page.content.indexOf("word-break: keep-all;") > -1;
1958 if (!hasWordBreakCss) {
1959 console.log("Japanese words can break across lines mid-word");
1960 console.log("Check CSS for '.phrase { word-break: keep-all; }'");
1961 fail();
1962 }
1963 // Run the next test
1964 next();
1965 });
1966 },
1967
1968 // Language can be specified at page load using hash value in url
1969 function() {
1970 page.open(url, function(status) {
1971 // Set the page hash as if it were on a fresh page load
1972 page.evaluate(function() {
1973 window.location.hash = "#japanese";
1974 });
1975 // Generate a random phrase
1976 page.evaluate(function() {
1977 $(".generate").trigger("click");
1978 });
1979 waitForGenerate(function() {
1980 // Check the phrase is in Japanese
1981 var phrase = page.evaluate(function() {
1982 return $(".phrase").val();
1983 });
1984 if (phrase.length <= 0) {
1985 console.log("No phrase generated using url hash");
1986 fail();
1987 }
1988 if (phrase.charCodeAt(0) < 128) {
1989 console.log("Language not detected from url hash on page load.");
1990 console.log("Phrase: " + phrase);
1991 fail();
1992 }
1993 next();
1994 });
1995 });
1996 },
1997
1998 // Entropy unit tests
1999 function() {
2000 page.open(url, function(status) {
2001 var response = page.evaluate(function() {
2002 var e;
2003 // binary entropy is detected
2004 try {
2005 e = Entropy.fromString("01010101");
2006 if (e.base.str != "binary") {
2007 return "Binary entropy not detected correctly";
2008 }
2009 }
2010 catch (e) {
2011 return e.message;
2012 }
2013 // base6 entropy is detected
2014 try {
2015 e = Entropy.fromString("012345012345");
2016 if (e.base.str != "base 6") {
2017 return "base6 entropy not detected correctly";
2018 }
2019 }
2020 catch (e) {
2021 return e.message;
2022 }
2023 // dice entropy is detected
2024 try {
2025 e = Entropy.fromString("123456123456");
2026 if (e.base.str != "base 6 (dice)") {
2027 return "dice entropy not detected correctly";
2028 }
2029 }
2030 catch (e) {
2031 return e.message;
2032 }
2033 // base10 entropy is detected
2034 try {
2035 e = Entropy.fromString("0123456789");
2036 if (e.base.str != "base 10") {
2037 return "base10 entropy not detected correctly";
2038 }
2039 }
2040 catch (e) {
2041 return e.message;
2042 }
2043 // hex entropy is detected
2044 try {
2045 e = Entropy.fromString("0123456789ABCDEF");
2046 if (e.base.str != "hexadecimal") {
2047 return "hexadecimal entropy not detected correctly";
2048 }
2049 }
2050 catch (e) {
2051 return e.message;
2052 }
2053 // card entropy is detected
2054 try {
2055 e = Entropy.fromString("AC4DTHKS");
2056 if (e.base.str != "card") {
2057 return "card entropy not detected correctly";
2058 }
2059 }
2060 catch (e) {
2061 return e.message;
2062 }
2063 // entropy is case insensitive
2064 try {
2065 e = Entropy.fromString("aBcDeF");
2066 if (e.cleanStr != "aBcDeF") {
2067 return "Entropy should not be case sensitive";
2068 }
2069 }
2070 catch (e) {
2071 return e.message;
2072 }
2073 // dice entropy is converted to base6
2074 try {
2075 e = Entropy.fromString("123456");
2076 if (e.cleanStr != "123450") {
2077 return "Dice entropy is not automatically converted to base6";
2078 }
2079 }
2080 catch (e) {
2081 return e.message;
2082 }
2083 // dice entropy is preferred to base6 if ambiguous
2084 try {
2085 e = Entropy.fromString("12345");
2086 if (e.base.str != "base 6 (dice)") {
2087 return "dice not used as default over base 6";
2088 }
2089 }
2090 catch (e) {
2091 return e.message;
2092 }
2093 // unused characters are ignored
2094 try {
2095 e = Entropy.fromString("fghijkl");
2096 if (e.cleanStr != "f") {
2097 return "additional characters are not ignored";
2098 }
2099 }
2100 catch (e) {
2101 return e.message;
2102 }
2103 // the lowest base is used by default
2104 // 7 could be decimal or hexadecimal, but should be detected as decimal
2105 try {
2106 e = Entropy.fromString("7");
2107 if (e.base.str != "base 10") {
2108 return "lowest base is not used";
2109 }
2110 }
2111 catch (e) {
2112 return e.message;
2113 }
2114 // Leading zeros are retained
2115 try {
2116 e = Entropy.fromString("000A");
2117 if (e.cleanStr != "000A") {
2118 return "Leading zeros are not retained";
2119 }
2120 }
2121 catch (e) {
2122 return e.message;
2123 }
2124 // Leading zeros are correctly preserved for hex in binary string
2125 try {
2126 e = Entropy.fromString("2A");
2127 if (e.binaryStr != "00101010") {
2128 return "Hex leading zeros are not correct in binary";
2129 }
2130 }
2131 catch (e) {
2132 return e.message;
2133 }
2134 // Leading zeros for base 6 as binary string
2135 // 20 = 2 events at 2.58 bits per event = 5 bits
2136 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2137 // so it needs 1 bit of padding to be the right bit length
2138 try {
2139 e = Entropy.fromString("20");
2140 if (e.binaryStr != "01100") {
2141 return "Base 6 as binary has leading zeros";
2142 }
2143 }
2144 catch (e) {
2145 return e.message;
2146 }
2147 // Leading zeros for base 10 as binary string
2148 try {
2149 e = Entropy.fromString("17");
2150 if (e.binaryStr != "010001") {
2151 return "Base 10 as binary has leading zeros";
2152 }
2153 }
2154 catch (e) {
2155 return e.message;
2156 }
2157 // Leading zeros for card entropy as binary string.
2158 // Card entropy is hashed so 2c does not produce leading zeros.
2159 try {
2160 e = Entropy.fromString("4c");
2161 if (e.binaryStr != "0001") {
2162 return "Card entropy as binary has leading zeros";
2163 }
2164 }
2165 catch (e) {
2166 return e.message;
2167 }
2168 // Keyboard mashing results in weak entropy
2169 // Despite being a long string, it's less than 30 bits of entropy
2170 try {
2171 e = Entropy.fromString("aj;se ifj; ask,dfv js;ifj");
2172 if (e.binaryStr.length >= 30) {
2173 return "Keyboard mashing should produce weak entropy";
2174 }
2175 }
2176 catch (e) {
2177 return e.message;
2178 }
2179 // Card entropy is used if every pair could be a card
2180 try {
2181 e = Entropy.fromString("4c3c2c");
2182 if (e.base.str != "card") {
2183 return "Card entropy not used if all pairs are cards";
2184 }
2185 }
2186 catch (e) {
2187 return e.message;
2188 }
2189 // Card entropy uses base 52
2190 // [ cards, binary ]
2191 try {
2192 var cards = [
2193 [ "ac", "0100" ],
2194 [ "acqs", "10111101" ],
2195 [ "acks", "11110000" ],
2196 [ "2cac", "11000010" ],
2197 [ "2c", "1000" ],
2198 [ "3d", "1111" ],
2199 [ "4h", "0011" ],
2200 [ "5s", "1001" ],
2201 [ "6c", "1011" ],
2202 [ "7d", "1101" ],
2203 [ "8h", "1011" ],
2204 [ "9s", "1010" ],
2205 [ "tc", "1101" ],
2206 [ "jd", "1101" ],
2207 [ "qh", "1100" ],
2208 [ "ks", "1111" ],
2209 [ "ks2c", "10000001" ],
2210 [ "KS2C", "10000001" ],
2211 ];
2212 for (var i=0; i<cards.length; i++) {
2213 var card = cards[i][0];
2214 var result = cards[i][1];
2215 e = Entropy.fromString(card);
2216 console.log(e.binary + " " + result);
2217 if (e.binaryStr !== result) {
2218 return "card entropy " + card + " not parsed correctly: " + result + " != " + e.binaryStr;
2219 }
2220 }
2221 }
2222 catch (e) {
2223 return e.message;
2224 }
2225 return "PASS";
2226 });
2227 if (response != "PASS") {
2228 console.log("Entropy unit tests");
2229 console.log(response);
2230 fail();
2231 };
2232 next();
2233 });
2234 },
2235
2236 // Entropy can be entered by the user
2237 function() {
2238 page.open(url, function(status) {
2239 expected = {
2240 mnemonic: "abandon abandon ability",
2241 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2242 }
2243 // use entropy
2244 page.evaluate(function() {
2245 $(".use-entropy").prop("checked", true).trigger("change");
2246 $(".mnemonic-length").val("raw");
2247 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2248 });
2249 // check the mnemonic is set and address is correct
2250 waitForGenerate(function() {
2251 var actual = page.evaluate(function() {
2252 return {
2253 address: $(".address:first").text(),
2254 mnemonic: $(".phrase").val(),
2255 }
2256 });
2257 if (actual.mnemonic != expected.mnemonic) {
2258 console.log("Entropy does not generate correct mnemonic");
2259 console.log("Expected: " + expected.mnemonic);
2260 console.log("Got: " + actual.mnemonic);
2261 fail();
2262 }
2263 if (actual.address != expected.address) {
2264 console.log("Entropy does not generate correct address");
2265 console.log("Expected: " + expected.address);
2266 console.log("Got: " + actual.address);
2267 fail();
2268 }
2269 next();
2270 });
2271 });
2272 },
2273
2274 // A warning about entropy is shown to the user, with additional information
2275 function() {
2276 page.open(url, function(status) {
2277 // get text content from entropy sections of page
2278 var hasWarning = page.evaluate(function() {
2279 var entropyText = $(".entropy-container").text();
2280 var warning = "mnemonic may be insecure";
2281 if (entropyText.indexOf(warning) == -1) {
2282 return false;
2283 }
2284 var readMoreText = $("#entropy-notes").parent().text();
2285 var goodSources = "flipping a fair coin, rolling a fair dice, noise measurements etc";
2286 if (readMoreText.indexOf(goodSources) == -1) {
2287 return false;
2288 }
2289 return true;
2290 });
2291 // check the warnings and information are shown
2292 if (!hasWarning) {
2293 console.log("Page does not contain warning about using own entropy");
2294 fail();
2295 }
2296 next();
2297 });
2298 },
2299
2300 // The types of entropy available are described to the user
2301 function() {
2302 page.open(url, function(status) {
2303 // get placeholder text for entropy field
2304 var placeholder = page.evaluate(function() {
2305 return $(".entropy").attr("placeholder");
2306 });
2307 var options = [
2308 "binary",
2309 "base 6",
2310 "dice",
2311 "base 10",
2312 "hexadecimal",
2313 "cards",
2314 ];
2315 for (var i=0; i<options.length; i++) {
2316 var option = options[i];
2317 if (placeholder.indexOf(option) == -1) {
2318 console.log("Available entropy type is not shown to user: " + option);
2319 fail();
2320 }
2321 }
2322 next();
2323 });
2324 },
2325
2326 // The actual entropy used is shown to the user
2327 function() {
2328 page.open(url, function(status) {
2329 // use entropy
2330 var badEntropySource = page.evaluate(function() {
2331 var entropy = "Not A Very Good Entropy Source At All";
2332 $(".use-entropy").prop("checked", true).trigger("change");
2333 $(".entropy").val(entropy).trigger("input");
2334 });
2335 // check the actual entropy being used is shown
2336 waitForEntropyFeedback(function() {
2337 var expectedText = "AedEceAA";
2338 var entropyText = page.evaluate(function() {
2339 return $(".entropy-container").text();
2340 });
2341 if (entropyText.indexOf(expectedText) == -1) {
2342 console.log("Actual entropy used is not shown");
2343 fail();
2344 }
2345 next();
2346 });
2347 });
2348 },
2349
2350 // Binary entropy can be entered
2351 function() {
2352 page.open(url, function(status) {
2353 // use entropy
2354 page.evaluate(function() {
2355 $(".use-entropy").prop("checked", true).trigger("change");
2356 $(".entropy").val("01").trigger("input");
2357 });
2358 // check the entropy is shown to be the correct type
2359 waitForEntropyFeedback(function() {
2360 var entropyText = page.evaluate(function() {
2361 return $(".entropy-container").text();
2362 });
2363 if (entropyText.indexOf("binary") == -1) {
2364 console.log("Binary entropy is not detected and presented to user");
2365 fail();
2366 }
2367 next();
2368 });
2369 });
2370 },
2371
2372 // Base 6 entropy can be entered
2373 function() {
2374 page.open(url, function(status) {
2375 // use entropy
2376 page.evaluate(function() {
2377 $(".use-entropy").prop("checked", true).trigger("change");
2378 $(".entropy").val("012345").trigger("input");
2379 });
2380 // check the entropy is shown to be the correct type
2381 waitForEntropyFeedback(function() {
2382 var entropyText = page.evaluate(function() {
2383 return $(".entropy-container").text();
2384 });
2385 if (entropyText.indexOf("base 6") == -1) {
2386 console.log("Base 6 entropy is not detected and presented to user");
2387 fail();
2388 }
2389 next();
2390 });
2391 });
2392 },
2393
2394 // Base 6 dice entropy can be entered
2395 function() {
2396 page.open(url, function(status) {
2397 // use entropy
2398 page.evaluate(function() {
2399 $(".use-entropy").prop("checked", true).trigger("change");
2400 $(".entropy").val("123456").trigger("input");
2401 });
2402 // check the entropy is shown to be the correct type
2403 waitForEntropyFeedback(function() {
2404 var entropyText = page.evaluate(function() {
2405 return $(".entropy-container").text();
2406 });
2407 if (entropyText.indexOf("dice") == -1) {
2408 console.log("Dice entropy is not detected and presented to user");
2409 fail();
2410 }
2411 next();
2412 });
2413 });
2414 },
2415
2416 // Base 10 entropy can be entered
2417 function() {
2418 page.open(url, function(status) {
2419 // use entropy
2420 page.evaluate(function() {
2421 $(".use-entropy").prop("checked", true).trigger("change");
2422 $(".entropy").val("789").trigger("input");
2423 });
2424 // check the entropy is shown to be the correct type
2425 waitForEntropyFeedback(function() {
2426 var entropyText = page.evaluate(function() {
2427 return $(".entropy-container").text();
2428 });
2429 if (entropyText.indexOf("base 10") == -1) {
2430 console.log("Base 10 entropy is not detected and presented to user");
2431 fail();
2432 }
2433 next();
2434 });
2435 });
2436 },
2437
2438 // Hexadecimal entropy can be entered
2439 function() {
2440 page.open(url, function(status) {
2441 // use entropy
2442 page.evaluate(function() {
2443 $(".use-entropy").prop("checked", true).trigger("change");
2444 $(".entropy").val("abcdef").trigger("input");
2445 });
2446 // check the entropy is shown to be the correct type
2447 waitForEntropyFeedback(function() {
2448 var entropyText = page.evaluate(function() {
2449 return $(".entropy-container").text();
2450 });
2451 if (entropyText.indexOf("hexadecimal") == -1) {
2452 console.log("Hexadecimal entropy is not detected and presented to user");
2453 fail();
2454 }
2455 next();
2456 });
2457 });
2458 },
2459
2460 // Dice entropy value is shown as the converted base 6 value
2461 function() {
2462 page.open(url, function(status) {
2463 // use entropy
2464 page.evaluate(function() {
2465 $(".use-entropy").prop("checked", true).trigger("change");
2466 $(".entropy").val("123456").trigger("input");
2467 });
2468 // check the entropy is shown as base 6, not as the original dice value
2469 waitForEntropyFeedback(function() {
2470 var entropyText = page.evaluate(function() {
2471 return $(".entropy-container").text();
2472 });
2473 if (entropyText.indexOf("123450") == -1) {
2474 console.log("Dice entropy is not shown to user as base 6 value");
2475 fail();
2476 }
2477 if (entropyText.indexOf("123456") > -1) {
2478 console.log("Dice entropy value is shown instead of true base 6 value");
2479 fail();
2480 }
2481 next();
2482 });
2483 });
2484 },
2485
2486 // The number of bits of entropy accumulated is shown
2487 function() {
2488 page.open(url, function(status) {
2489 //[ entropy, bits ]
2490 var tests = [
2491 [ "0000 0000 0000 0000 0000", "20" ],
2492 [ "0", "1" ],
2493 [ "0000", "4" ],
2494 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2495 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2496 [ "8", "4" ],
2497 [ "F", "4" ],
2498 [ "29", "6" ],
2499 [ "0A", "8" ],
2500 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2501 [ "2A", "8" ],
2502 [ "4A", "8" ],
2503 [ "8A", "8" ],
2504 [ "FA", "8" ],
2505 [ "000A", "16" ],
2506 [ "5555", "11" ],
2507 [ "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)
2508 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2509 [ "222F", "16" ],
2510 [ "FFFF", "16" ],
2511 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2512 [ "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
2513 ]
2514 // use entropy
2515 page.evaluate(function(e) {
2516 $(".use-entropy").prop("checked", true).trigger("change");
2517 });
2518 // Run each test
2519 var nextTest = function runNextTest(i) {
2520 var entropy = tests[i][0];
2521 var expected = tests[i][1];
2522 // set entropy
2523 page.evaluate(function(e) {
2524 $(".entropy").val(e).trigger("input");
2525 }, entropy);
2526 // check the number of bits of entropy is shown
2527 waitForEntropyFeedback(function() {
2528 var entropyText = page.evaluate(function() {
2529 return $(".entropy-container").text();
2530 });
2531 if (entropyText.replace(/\s/g,"").indexOf("Bits" + expected) == -1) {
2532 console.log("Accumulated entropy is not shown correctly for " + entropy);
2533 fail();
2534 }
2535 var isLastTest = i == tests.length - 1;
2536 if (isLastTest) {
2537 next();
2538 }
2539 else {
2540 runNextTest(i+1);
2541 }
2542 });
2543 }
2544 nextTest(0);
2545 });
2546 },
2547
2548 // There is feedback provided about the supplied entropy
2549 function() {
2550 page.open(url, function(status) {
2551 var tests = [
2552 {
2553 entropy: "A",
2554 filtered: "A",
2555 type: "hexadecimal",
2556 events: 1,
2557 bits: 4,
2558 words: 0,
2559 strength: "extremely weak",
2560 },
2561 {
2562 entropy: "AAAAAAAA",
2563 filtered: "AAAAAAAA",
2564 type: "hexadecimal",
2565 events: 8,
2566 bits: 32,
2567 words: 3,
2568 strength: "extremely weak",
2569 },
2570 {
2571 entropy: "AAAAAAAA B",
2572 filtered: "AAAAAAAAB",
2573 type: "hexadecimal",
2574 events: 9,
2575 bits: 36,
2576 words: 3,
2577 strength: "extremely weak",
2578 },
2579 {
2580 entropy: "AAAAAAAA BBBBBBBB",
2581 filtered: "AAAAAAAABBBBBBBB",
2582 type: "hexadecimal",
2583 events: 16,
2584 bits: 64,
2585 words: 6,
2586 strength: "very weak",
2587 },
2588 {
2589 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2590 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2591 type: "hexadecimal",
2592 events: 24,
2593 bits: 96,
2594 words: 9,
2595 strength: "weak",
2596 },
2597 {
2598 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2599 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2600 type: "hexadecimal",
2601 events: 32,
2602 bits: 128,
2603 words: 12,
2604 strength: "easily cracked",
2605 },
2606 {
2607 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2608 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2609 type: "hexadecimal",
2610 events: 32,
2611 bits: 128,
2612 words: 12,
2613 strength: "strong",
2614 },
2615 {
2616 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2617 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2618 type: "hexadecimal",
2619 events: 40,
2620 bits: 160,
2621 words: 15,
2622 strength: "very strong",
2623 },
2624 {
2625 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2626 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2627 type: "hexadecimal",
2628 events: 48,
2629 bits: 192,
2630 words: 18,
2631 strength: "extremely strong",
2632 },
2633 {
2634 entropy: "7d",
2635 type: "card",
2636 events: 1,
2637 bits: 5,
2638 words: 0,
2639 strength: "extremely weak",
2640 },
2641 {
2642 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2643 type: "card (full deck)",
2644 events: 52,
2645 bits: 225,
2646 words: 21,
2647 strength: "extremely strong",
2648 },
2649 {
2650 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2651 type: "card (full deck, 1 duplicate: 3d)",
2652 events: 53,
2653 bits: 254,
2654 words: 21,
2655 strength: "extremely strong",
2656 },
2657 {
2658 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2659 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2660 events: 53,
2661 bits: 254,
2662 words: 21,
2663 strength: "extremely strong",
2664 },
2665 {
2666 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2667 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2668 events: 53,
2669 bits: 264,
2670 words: 24,
2671 strength: "extremely strong",
2672 },
2673 // Next test was throwing uncaught error in zxcvbn
2674 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2675 {
2676 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2677 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2678 events: 104,
2679 bits: 499,
2680 words: 45,
2681 strength: "extremely strong",
2682 },
2683 // Case insensitivity to duplicate cards
2684 {
2685 entropy: "asAS",
2686 type: "card (1 duplicate: AS)",
2687 events: 2,
2688 bits: 9,
2689 words: 0,
2690 strength: "extremely weak",
2691 },
2692 {
2693 entropy: "ASas",
2694 type: "card (1 duplicate: as)",
2695 events: 2,
2696 bits: 9,
2697 words: 0,
2698 strength: "extremely weak",
2699 },
2700 // Missing cards are detected
2701 {
2702 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2703 type: "card (1 missing: 9C)",
2704 events: 51,
2705 bits: 221,
2706 words: 18,
2707 strength: "extremely strong",
2708 },
2709 {
2710 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2711 type: "card (2 missing: 9C 5D)",
2712 events: 50,
2713 bits: 216,
2714 words: 18,
2715 strength: "extremely strong",
2716 },
2717 {
2718 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2719 type: "card (4 missing: 9C 5D QD...)",
2720 events: 48,
2721 bits: 208,
2722 words: 18,
2723 strength: "extremely strong",
2724 },
2725 // More than six missing cards does not show message
2726 {
2727 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2728 type: "card",
2729 events: 45,
2730 bits: 195,
2731 words: 18,
2732 strength: "extremely strong",
2733 },
2734 // Multiple decks of cards increases bits per event
2735 {
2736 entropy: "3d",
2737 events: 1,
2738 bits: 4,
2739 bitsPerEvent: 4.34,
2740 },
2741 {
2742 entropy: "3d3d",
2743 events: 2,
2744 bits: 9,
2745 bitsPerEvent: 4.80,
2746 },
2747 {
2748 entropy: "3d3d3d",
2749 events: 3,
2750 bits: 15,
2751 bitsPerEvent: 5.01,
2752 },
2753 {
2754 entropy: "3d3d3d3d",
2755 events: 4,
2756 bits: 20,
2757 bitsPerEvent: 5.14,
2758 },
2759 {
2760 entropy: "3d3d3d3d3d",
2761 events: 5,
2762 bits: 26,
2763 bitsPerEvent: 5.22,
2764 },
2765 {
2766 entropy: "3d3d3d3d3d3d",
2767 events: 6,
2768 bits: 31,
2769 bitsPerEvent: 5.28,
2770 },
2771 {
2772 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2773 events: 33,
2774 bits: 184,
2775 bitsPerEvent: 5.59,
2776 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2777 },
2778 ];
2779 // use entropy
2780 page.evaluate(function() {
2781 $(".use-entropy").prop("checked", true).trigger("change");
2782 $(".mnemonic-length").val("raw");
2783 });
2784 var nextTest = function runNextTest(i) {
2785 function getFeedbackError(expected, actual) {
2786 if ("filtered" in expected && actual.indexOf(expected.filtered) == -1) {
2787 return "Filtered value not in feedback";
2788 }
2789 if ("type" in expected && actual.indexOf(expected.type) == -1) {
2790 return "Entropy type not in feedback";
2791 }
2792 if ("events" in expected && actual.indexOf(expected.events) == -1) {
2793 return "Event count not in feedback";
2794 }
2795 if ("bits" in expected && actual.indexOf(expected.bits) == -1) {
2796 return "Bit count not in feedback";
2797 }
2798 if ("strength" in expected && actual.indexOf(expected.strength) == -1) {
2799 return "Strength not in feedback";
2800 }
2801 if ("bitsPerEvent" in expected && actual.indexOf(expected.bitsPerEvent) == -1) {
2802 return "bitsPerEvent not in feedback";
2803 }
2804 return false;
2805 }
2806 test = tests[i];
2807 page.evaluate(function(e) {
2808 $(".addresses").empty();
2809 $(".phrase").val("");
2810 $(".entropy").val(e).trigger("input");
2811 }, test.entropy);
2812 waitForEntropyFeedback(function() {
2813 var mnemonic = page.evaluate(function() {
2814 return $(".phrase").val();
2815 });
2816 // Check mnemonic length
2817 if ("words" in test && test.words == 0) {
2818 if (mnemonic.length > 0) {
2819 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2820 console.log("Entropy: " + test.entropy);
2821 console.log("Mnemonic: " + mnemonic);
2822 fail();
2823 }
2824 }
2825 else if ("words" in test) {
2826 if (mnemonic.split(" ").length != test.words) {
2827 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2828 console.log("Entropy: " + test.entropy);
2829 console.log("Mnemonic: " + mnemonic);
2830 fail();
2831 }
2832 }
2833 // check feedback
2834 var feedback = page.evaluate(function() {
2835 return $(".entropy-container").text();
2836 });
2837 var feedbackError = getFeedbackError(test, feedback);
2838 if (feedbackError) {
2839 console.log("Entropy feedback for " + test.entropy + " returned error");
2840 console.log(feedbackError);
2841 fail();
2842 }
2843 // Run next test
2844 var isLastTest = i == tests.length - 1;
2845 if (isLastTest) {
2846 next();
2847 }
2848 else {
2849 runNextTest(i+1);
2850 }
2851 });
2852 }
2853 nextTest(0);
2854 });
2855 },
2856
2857 // Entropy is truncated from the left
2858 function() {
2859 page.open(url, function(status) {
2860 var expected = "avocado zoo zone";
2861 // use entropy
2862 page.evaluate(function() {
2863 $(".use-entropy").prop("checked", true).trigger("change");
2864 $(".mnemonic-length").val("raw");
2865 var entropy = "00000000 00000000 00000000 00000000";
2866 entropy += "11111111 11111111 11111111 1111"; // Missing last byte
2867 $(".entropy").val(entropy).trigger("input");
2868 });
2869 // check the entropy is truncated from the right
2870 waitForGenerate(function() {
2871 var actual = page.evaluate(function() {
2872 return $(".phrase").val();
2873 });
2874 if (actual != expected) {
2875 console.log("Entropy is not truncated from the right");
2876 console.log("Expected: " + expected);
2877 console.log("Got: " + actual);
2878 fail();
2879 }
2880 next();
2881 });
2882 });
2883 },
2884
2885 // Very large entropy results in very long mnemonics
2886 function() {
2887 page.open(url, function(status) {
2888 // use entropy
2889 page.evaluate(function() {
2890 $(".use-entropy").prop("checked", true).trigger("change");
2891 $(".mnemonic-length").val("raw");
2892 var entropy = "";
2893 // Generate a very long entropy string
2894 for (var i=0; i<33; i++) {
2895 entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2896 }
2897 $(".entropy").val(entropy).trigger("input");
2898 });
2899 // check the mnemonic is very long
2900 waitForGenerate(function() {
2901 var wordCount = page.evaluate(function() {
2902 return $(".phrase").val().split(" ").length;
2903 });
2904 if (wordCount != 99) {
2905 console.log("Large entropy does not generate long mnemonic");
2906 console.log("Expected 99 words, got " + wordCount);
2907 fail();
2908 }
2909 next();
2910 });
2911 });
2912 },
2913
2914 // Is compatible with bip32jp entropy
2915 // https://bip32jp.github.io/english/index.html
2916 // NOTES:
2917 // Is incompatible with:
2918 // base 20
2919 function() {
2920 page.open(url, function(status) {
2921 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";
2922 // use entropy
2923 page.evaluate(function() {
2924 $(".use-entropy").prop("checked", true).trigger("change");
2925 $(".mnemonic-length").val("raw");
2926 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2927 $(".entropy").val(entropy).trigger("input");
2928 });
2929 // check the mnemonic matches the expected value from bip32jp
2930 waitForGenerate(function() {
2931 var actual = page.evaluate(function() {
2932 return $(".phrase").val();
2933 });
2934 if (actual != expected) {
2935 console.log("Mnemonic does not match bip32jp for base 6 entropy");
2936 console.log("Expected: " + expected);
2937 console.log("Got: " + actual);
2938 fail();
2939 }
2940 next();
2941 });
2942 });
2943 },
2944
2945 // Blank entropy does not generate mnemonic or addresses
2946 function() {
2947 page.open(url, function(status) {
2948 // use entropy
2949 page.evaluate(function() {
2950 $(".use-entropy").prop("checked", true).trigger("change");
2951 $(".entropy").val("").trigger("input");
2952 });
2953 waitForFeedback(function() {
2954 // check there is no mnemonic
2955 var phrase = page.evaluate(function() {
2956 return $(".phrase").val();
2957 });
2958 if (phrase != "") {
2959 console.log("Blank entropy does not result in blank mnemonic");
2960 console.log("Got: " + phrase);
2961 fail();
2962 }
2963 // check there are no addresses displayed
2964 var addresses = page.evaluate(function() {
2965 return $(".address").length;
2966 });
2967 if (addresses != 0) {
2968 console.log("Blank entropy does not result in zero addresses");
2969 fail();
2970 }
2971 // Check the feedback says 'blank entropy'
2972 var feedback = page.evaluate(function() {
2973 return $(".feedback").text();
2974 });
2975 if (feedback != "Blank entropy") {
2976 console.log("Blank entropy does not show feedback message");
2977 fail();
2978 }
2979 next();
2980 });
2981 });
2982 },
2983
2984 // Mnemonic length can be selected even for weak entropy
2985 function() {
2986 page.open(url, function(status) {
2987 // use entropy
2988 page.evaluate(function() {
2989 $(".use-entropy").prop("checked", true).trigger("change");
2990 $(".entropy").val("012345");
2991 $(".mnemonic-length").val("18").trigger("change");
2992 });
2993 // check the mnemonic is the correct length
2994 waitForGenerate(function() {
2995 var phrase = page.evaluate(function() {
2996 return $(".phrase").val();
2997 });
2998 var numberOfWords = phrase.split(/\s/g).length;
2999 if (numberOfWords != 18) {
3000 console.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3001 console.log(phrase);
3002 fail();
3003 }
3004 next();
3005 });
3006 });
3007 },
3008
3009 // Github issue 33
3010 // https://github.com/iancoleman/bip39/issues/33
3011 // Final cards should contribute entropy
3012 function() {
3013 page.open(url, function(status) {
3014 // use entropy
3015 page.evaluate(function() {
3016 $(".use-entropy").prop("checked", true).trigger("change");
3017 $(".mnemonic-length").val("raw");
3018 $(".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");
3019 });
3020 // get the mnemonic
3021 waitForGenerate(function() {
3022 var originalPhrase = page.evaluate(function() {
3023 return $(".phrase").val();
3024 });
3025 // Set the last 12 cards to be AS
3026 page.evaluate(function() {
3027 $(".addresses").empty();
3028 $(".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");
3029 });
3030 // get the new mnemonic
3031 waitForGenerate(function() {
3032 var newPhrase = page.evaluate(function() {
3033 return $(".phrase").val();
3034 });
3035 // check the phrase has changed
3036 if (newPhrase == originalPhrase) {
3037 console.log("Changing last 12 cards does not change mnemonic");
3038 console.log("Original:");
3039 console.log(originalPhrase);
3040 console.log("New:");
3041 console.log(newPhrase);
3042 fail();
3043 }
3044 next();
3045 });
3046 });
3047 });
3048 },
3049
3050 // Github issue 35
3051 // https://github.com/iancoleman/bip39/issues/35
3052 // QR Code support
3053 function() {
3054 page.open(url, function(status) {
3055 // use entropy
3056 page.evaluate(function() {
3057 $(".generate").click();
3058 });
3059 waitForGenerate(function() {
3060 var p = page.evaluate(function() {
3061 // get position of mnemonic element
3062 return $(".phrase").offset();
3063 });
3064 p.top = Math.ceil(p.top);
3065 p.left = Math.ceil(p.left);
3066 // check the qr code shows
3067 page.sendEvent("mousemove", p.left+4, p.top+4);
3068 var qrShowing = page.evaluate(function() {
3069 return $(".qr-container").find("canvas").length > 0;
3070 });
3071 if (!qrShowing) {
3072 console.log("QR Code does not show");
3073 fail();
3074 }
3075 // check the qr code hides
3076 page.sendEvent("mousemove", p.left-4, p.top-4);
3077 var qrHidden = page.evaluate(function() {
3078 return $(".qr-container").find("canvas").length == 0;
3079 });
3080 if (!qrHidden) {
3081 console.log("QR Code does not hide");
3082 fail();
3083 }
3084 next();
3085 });
3086 });
3087 },
3088
3089 // BIP44 account extendend private key is shown
3090 // github issue 37 - compatibility with electrum
3091 function() {
3092 page.open(url, function(status) {
3093 // set the phrase
3094 var expected = "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3095 page.evaluate(function() {
3096 $(".phrase").val("abandon abandon ability");
3097 $(".phrase").trigger("input");
3098 });
3099 // check the BIP44 account extended private key
3100 waitForGenerate(function() {
3101 var actual = page.evaluate(function() {
3102 return $(".account-xprv").val();
3103 });
3104 if (actual != expected) {
3105 console.log("BIP44 account extended private key is incorrect");
3106 console.log("Expected: " + expected);
3107 console.log("Actual: " + actual);
3108 fail();
3109 }
3110 next();
3111 });
3112 });
3113 },
3114
3115 // BIP44 account extendend public key is shown
3116 // github issue 37 - compatibility with electrum
3117 function() {
3118 page.open(url, function(status) {
3119 // set the phrase
3120 var expected = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3121 page.evaluate(function() {
3122 $(".phrase").val("abandon abandon ability");
3123 $(".phrase").trigger("input");
3124 });
3125 // check the BIP44 account extended public key
3126 waitForGenerate(function() {
3127 var actual = page.evaluate(function() {
3128 return $(".account-xpub").val();
3129 });
3130 if (actual != expected) {
3131 console.log("BIP44 account extended public key is incorrect");
3132 console.log("Expected: " + expected);
3133 console.log("Actual: " + actual);
3134 fail();
3135 }
3136 next();
3137 });
3138 });
3139 },
3140
3141 // github issue 40
3142 // BIP32 root key can be set as an xpub
3143 function() {
3144 page.open(url, function(status) {
3145 // set the phrase
3146 page.evaluate(function() {
3147 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3148 var bip44AccountXpub = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3149 $("#root-key").val(bip44AccountXpub);
3150 $("#root-key").trigger("input");
3151 });
3152 waitForFeedback(function() {
3153 page.evaluate(function() {
3154 // Use bip32 tab
3155 $("#bip32-tab a").click();
3156 });
3157 waitForGenerate(function() {
3158 page.evaluate(function() {
3159 // derive external addresses for this xpub
3160 var firstAccountDerivationPath = "m/0";
3161 $("#bip32-path").val(firstAccountDerivationPath);
3162 $("#bip32-path").trigger("input");
3163 });
3164 waitForGenerate(function() {
3165 // check the addresses are generated
3166 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3167 var actual = page.evaluate(function() {
3168 return $(".address:first").text();
3169 });
3170 if (actual != expected) {
3171 console.log("xpub key does not generate addresses in table");
3172 console.log("Expected: " + expected);
3173 console.log("Actual: " + actual);
3174 fail();
3175 }
3176 // check the xprv key is not set
3177 var expected = "NA";
3178 var actual = page.evaluate(function() {
3179 return $(".extended-priv-key").val();
3180 });
3181 if (actual != expected) {
3182 console.log("xpub key as root shows derived bip32 xprv key");
3183 console.log("Expected: " + expected);
3184 console.log("Actual: " + actual);
3185 fail();
3186 }
3187 // check the private key is not set
3188 var expected = "NA";
3189 var actual = page.evaluate(function() {
3190 return $(".privkey:first").text();
3191 });
3192 if (actual != expected) {
3193 console.log("xpub key generates private key in addresses table");
3194 console.log("Expected: " + expected);
3195 console.log("Actual: " + actual);
3196 fail();
3197 }
3198 next();
3199 });
3200 });
3201 });
3202 });
3203 },
3204
3205 // github issue 40
3206 // xpub for bip32 root key will not work with hardened derivation paths
3207 function() {
3208 page.open(url, function(status) {
3209 // set the phrase
3210 page.evaluate(function() {
3211 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3212 var bip44AccountXpub = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3213 $("#root-key").val(bip44AccountXpub);
3214 $("#root-key").trigger("input");
3215 });
3216 waitForFeedback(function() {
3217 // Check feedback is correct
3218 var expected = "Hardened derivation path is invalid with xpub key";
3219 var actual = page.evaluate(function() {
3220 return $(".feedback").text();
3221 });
3222 if (actual != expected) {
3223 console.log("xpub key with hardened derivation path does not show feedback");
3224 console.log("Expected: " + expected);
3225 console.log("Actual: " + actual);
3226 fail();
3227 }
3228 // Check no addresses are shown
3229 var expected = 0;
3230 var actual = page.evaluate(function() {
3231 return $(".addresses tr").length;
3232 });
3233 if (actual != expected) {
3234 console.log("addresses still show after setting xpub key with hardened derivation path");
3235 console.log("Expected: " + expected);
3236 console.log("Actual: " + actual);
3237 fail();
3238 }
3239 next();
3240 });
3241 });
3242 },
3243
3244
3245 // If you wish to add more tests, do so here...
3246
3247 // Here is a blank test template
3248 /*
3249
3250 function() {
3251 page.open(url, function(status) {
3252 // Do something on the page
3253 page.evaluate(function() {
3254 $(".phrase").val("abandon abandon ability").trigger("input");
3255 });
3256 waitForGenerate(function() {
3257 // Check the result of doing the thing
3258 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3259 var actual = page.evaluate(function() {
3260 return $(".address:first").text();
3261 });
3262 if (actual != expected) {
3263 console.log("A specific message about what failed");
3264 console.log("Expected: " + expected);
3265 console.log("Actual: " + actual);
3266 fail();
3267 }
3268 // Run the next test
3269 next();
3270 });
3271 });
3272 },
3273
3274 */
3275
3276 ];
3277
3278 console.log("Running tests...");
3279 tests = shuffle(tests);
3280 next();