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