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