2 // cd /path/to/repo/tests
3 // jasmine spec/tests.js
9 // see https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode#Automated_testing_with_headless_mode
11 // USER SPECIFIED OPTIONS
12 var browser
= process
.env
.BROWSER
; //"firefox"; // or "chrome"
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");
21 console
.log("Using browser: " + browser
);
26 var webdriver
= require('selenium-webdriver');
27 var By
= webdriver
.By
;
28 var Key
= webdriver
.Key
;
29 var until
= webdriver
.until
;
33 var generateDelay
= 1000;
34 var feedbackDelay
= 500;
35 var entropyFeedbackDelay
= 500;
37 // url uses file:// scheme
38 var path
= require('path')
39 var parentDir
= path
.resolve(process
.cwd(), '..', 'src', 'index.html');
40 var url
= "file://" + parentDir
;
41 if (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";
50 // Variables dependent on specific browser selection
52 if (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
))
63 else if (browser
== "chrome") {
64 var chrome
= require('selenium-webdriver/chrome');
65 newDriver = function() {
66 return new webdriver
.Builder()
68 .setChromeOptions(new chrome
.Options().addArguments("headless"))
75 function testNetwork(done
, params
) {
76 var phrase
= params
.phrase
|| 'abandon abandon ability';
77 driver
.findElement(By
.css('.phrase'))
79 selectNetwork(params
.selectText
);
80 driver
.sleep(generateDelay
).then(function() {
81 getFirstAddress(function(address
) {
82 expect(address
).toBe(params
.firstAddress
);
88 function getFirstRowValue(handler
, selector
) {
89 driver
.findElements(By
.css(selector
))
96 function getFirstAddress(handler
) {
97 getFirstRowValue(handler
, ".address");
100 function getFirstPath(handler
) {
101 getFirstRowValue(handler
, ".index");
104 function 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");
116 function testRowsAreInCorrectOrder(done
) {
117 driver
.findElements(By
.css('.index'))
118 .then(function(els
) {
119 var testRowAtIndex = function(i
) {
120 if (i
>= els
.length
) {
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);
139 function 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");
150 function 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
'))
156 driver.findElement(By.css('.entropy
'))
157 .sendKeys(entropyText);
158 driver.sleep(generateDelay).then(function() {
159 driver.findElement(By.css('.entropy
-container
'))
161 .then(function(text) {
162 var re = new RegExp("Entropy Type\\s+" + entropyType);
163 expect(text).toMatch(re);
169 function testEntropyBits(done, entropyText, entropyBits) {
170 driver.findElement(By.css('.use-entropy
'))
172 driver.findElement(By.css('.entropy
'))
173 .sendKeys(entropyText);
174 driver.sleep(generateDelay).then(function() {
175 driver.findElement(By.css('.entropy
-container
'))
177 .then(function(text) {
178 var re = new RegExp("Total Bits\\s+" + entropyBits);
179 expect(text).toMatch(re);
185 function 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, "\\$&");
191 driver.findElement(By.css('.use-entropy
'))
193 driver.findElement(By.css('.entropy
'))
194 .sendKeys(entropyDetail.entropy);
195 driver.sleep(entropyFeedbackDelay).then(function() {
196 driver.findElement(By.css('.entropy
-container
'))
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);
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);
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);
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);
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);
237 if ("words" in entropyDetail) {
238 var actualWords = phrase.split(/\s+/)
239 .filter(function(w) { return w.length > 0 })
241 expect(actualWords).toBe(entropyDetail.words);
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);
256 function testClientSelect(done, params) {
257 // set mnemonic and select bip32 tab
258 driver.findElement(By.css('#bip32
-tab a
'))
260 driver.findElement(By.css('.phrase
'))
261 .sendKeys("abandon abandon ability");
262 driver.sleep(generateDelay).then(function() {
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");
294 describe('BIP39 Tool Tests
', function() {
296 beforeEach(function(done) {
297 driver = newDriver();
298 driver.get(url).then(done);
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);
308 // Page initially loads with blank phrase
309 it('Should load the page
', function(done) {
310 driver.findElement(By.css('.phrase
'))
311 .getAttribute('value
').then(function(value) {
312 expect(value).toBe('');
318 it('Should have text on the page
', function(done) {
319 driver.findElement(By.css('body
'))
321 .then(function(text) {
322 var textToFind = "You can enter an existing BIP39 mnemonic";
323 expect(text).toContain(textToFind);
328 // Entering mnemonic generates addresses
329 it('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);
341 // Generate button generates random mnemonic
342 it('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);
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);
362 // Mnemonic length can be customized
363 it('Should allow custom length mnemonics
', function(done) {
365 driver.executeScript(function() {
366 $(".strength option[selected]").removeAttr("selected");
367 $(".strength option[value=6]").prop("selected", true);
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);
381 // Passphrase can be set
382 it('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");
395 // Network can be set to networks other than bitcoin
396 it('Allows selection
of bitcoin testnet
', function(done) {
398 selectText: "BTC - Bitcoin Testnet",
399 firstAddress: "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi",
401 testNetwork(done, params);
403 it('Allows selection
of litecoin
', function(done) {
405 selectText: "LTC - Litecoin",
406 firstAddress: "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn",
408 testNetwork(done, params);
410 it('Allows selection
of ripple
', function(done) {
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",
416 testNetwork(done, params);
418 it('Allows selection
of dogecoin
', function(done) {
420 selectText: "DOGE - Dogecoin",
421 firstAddress: "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA",
423 testNetwork(done, params);
425 it('Allows selection
of shadowcash
', function(done) {
427 selectText: "SDC - ShadowCash",
428 firstAddress: "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG",
430 testNetwork(done, params);
432 it('Allows selection
of shadowcash testnet
', function(done) {
434 selectText: "SDC - ShadowCash Testnet",
435 firstAddress: "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe",
437 testNetwork(done, params);
439 it('Allows selection
of viacoin
', function(done) {
441 selectText: "VIA - Viacoin",
442 firstAddress: "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT",
444 testNetwork(done, params);
446 it('Allows selection
of viacoin testnet
', function(done) {
448 selectText: "VIA - Viacoin Testnet",
449 firstAddress: "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe",
451 testNetwork(done, params);
453 it('Allows selection
of jumbucks
', function(done) {
455 selectText: "JBS - Jumbucks",
456 firstAddress: "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew",
458 testNetwork(done, params);
460 it('Allows selection
of clam
', function(done) {
462 selectText: "CLAM - Clams",
463 firstAddress: "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y",
465 testNetwork(done, params);
467 it('Allows selection
of crown
', function(done) {
469 selectText: "CRW - Crown",
470 firstAddress: "18pWSwSUAQdiwMHUfFZB1fM2xue9X1FqE5",
472 testNetwork(done, params);
474 it('Allows selection
of dash
', function(done) {
476 selectText: "DASH - Dash",
477 firstAddress: "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT",
479 testNetwork(done, params);
481 it('Allows selection
of dash testnet
', function(done) {
483 selectText: "DASH - Dash Testnet",
484 firstAddress: "yaR52EN4oojdJfBgzWJTymC4uuCLPT29Gw",
486 testNetwork(done, params);
488 it('Allows selection
of game
', function(done) {
490 selectText: "GAME - GameCredits",
491 firstAddress: "GSMY9bAp36cMR4zyT4uGVS7GFjpdXbao5Q",
493 testNetwork(done, params);
495 it('Allows selection
of game
', function(done) {
497 selectText: "KMD - Komodo",
498 firstAddress: "RJL777dmaB3PYqHEJGMJKWWkLPdu1ypGi4",
500 testNetwork(done, params);
502 it('Allows selection
of namecoin
', function(done) {
504 selectText: "NMC - Namecoin",
505 firstAddress: "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2",
507 testNetwork(done, params);
509 it('Allows selection
of peercoin
', function(done) {
511 selectText: "PPC - Peercoin",
512 firstAddress: "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm",
514 testNetwork(done, params);
516 it('Allows selection
of ethereum
', function(done) {
518 selectText: "ETH - Ethereum",
519 firstAddress: "0xe5815d5902Ad612d49283DEdEc02100Bd44C2772",
521 testNetwork(done, params);
522 // TODO test private key and public key
524 it('Allows selection
of slimcoin
', function(done) {
526 selectText: "SLM - Slimcoin",
527 firstAddress: "SNzPi1CafHFm3WWjRo43aMgiaEEj3ogjww",
529 testNetwork(done, params);
531 it('Allows selection
of slimcoin testnet
', function(done) {
533 selectText: "SLM - Slimcoin Testnet",
534 firstAddress: "n3nMgWufTek5QQAr6uwMhg5xbzj8xqc4Dq",
536 testNetwork(done, params);
538 it('Allows selection
of bitcoin cash
', function(done) {
540 selectText: "BCH - Bitcoin Cash",
541 firstAddress: "1JKvb6wKtsjNoCRxpZ4DGrbniML7z5U16A",
543 testNetwork(done, params);
545 it('Allows selection
of myriadcoin
', function(done) {
547 selectText: "XMY - Myriadcoin",
548 firstAddress: "MJEswvRR46wh9BoiVj9DzKYMBkCramhoBV",
550 testNetwork(done, params);
552 it('Allows selection
of pivx
', function(done) {
554 selectText: "PIVX - PIVX",
555 firstAddress: "DBxgT7faCuno7jmtKuu6KWCiwqsVPqh1tS",
557 testNetwork(done, params);
559 it('Allows selection
of pivx testnet
', function(done) {
561 selectText: "PIVX - PIVX Testnet",
562 firstAddress: "yB5U384n6dGkVE3by5y9VdvHHPwPg68fQj",
564 testNetwork(done, params);
566 it('Allows selection
of maza
', function(done) {
568 selectText: "MAZA - Maza",
569 firstAddress: "MGW4Bmi2NEm4PxSjgeFwhP9vg18JHoRnfw",
571 testNetwork(done, params);
573 it('Allows selection
of fujicoin
', function(done) {
575 selectText: "FJC - Fujicoin",
576 firstAddress: "FgiaLpG7C99DyR4WnPxXedRVHXSfKzUDhF",
578 testNetwork(done, params);
580 it('Allows selection
of nubits
', function(done) {
582 selectText: "USNBT - NuBits",
583 firstAddress: "BLxkabXuZSJSdesLD7KxZdqovd4YwyBTU6",
585 testNetwork(done, params);
587 it('Allows selection
of bitcoin gold
', function(done) {
589 selectText: "BTG - Bitcoin Gold",
590 firstAddress: "GWYxuwSqANWGV3WT7Gpr6HE91euYXBqtwQ",
592 testNetwork(done, params);
594 it('Allows selection
of monacoin
', function(done) {
596 selectText: "MONA - Monacoin",
597 firstAddress: "MKMiMr7MyjDKjJbCBzgF6u4ByqTS4NkRB1",
599 testNetwork(done, params);
602 // BIP39 seed is set from phrase
603 it('Sets the bip39 seed
from the prhase
', function(done) {
604 driver.findElement(By.css('.phrase
'))
605 .sendKeys('abandon abandon ability
');
606 driver.sleep(generateDelay).then(function() {
607 driver.findElement(By.css('.seed
'))
608 .getAttribute("value")
609 .then(function(seed) {
610 expect(seed).toBe("20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3");
616 // BIP32 root key is set from phrase
617 it('Sets the bip39 root key
from the prhase
', function(done) {
618 driver.findElement(By.css('.phrase
'))
619 .sendKeys('abandon abandon ability
');
620 driver.sleep(generateDelay).then(function() {
621 driver.findElement(By.css('.root
-key
'))
622 .getAttribute("value")
623 .then(function(seed) {
624 expect(seed).toBe("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi");
630 // Tabs show correct addresses when changed
631 it('Shows the correct address when tab is changed
', function(done) {
632 driver.findElement(By.css('.phrase
'))
633 .sendKeys('abandon abandon ability
');
634 driver.sleep(generateDelay).then(function() {
635 driver.findElement(By.css('#bip32
-tab a
'))
637 driver.sleep(generateDelay).then(function() {
638 getFirstAddress(function(address) {
639 expect(address).toBe("17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz");
646 // BIP44 derivation path is shown
647 it('Shows the derivation path
for bip44 tab
', function(done) {
648 driver.findElement(By.css('.phrase
'))
649 .sendKeys('abandon abandon ability
');
650 driver.sleep(generateDelay).then(function() {
651 driver.findElement(By.css('#bip44
.path
'))
652 .getAttribute("value")
653 .then(function(path) {
654 expect(path).toBe("m/44'/0'/0'/0");
660 // BIP44 extended private key is shown
661 it('Shows the extended private key for bip44 tab', function(done) {
662 driver.findElement(By.css('.phrase'))
663 .sendKeys('abandon abandon ability');
664 driver.sleep(generateDelay).then(function() {
665 driver.findElement(By.css('.extended-priv-key'))
666 .getAttribute("value
")
667 .then(function(path) {
668 expect(path).toBe("xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG
");
674 // BIP44 extended public key is shown
675 it('Shows the extended public key for bip44 tab', function(done) {
676 driver.findElement(By.css('.phrase'))
677 .sendKeys('abandon abandon ability');
678 driver.sleep(generateDelay).then(function() {
679 driver.findElement(By.css('.extended-pub-key'))
680 .getAttribute("value
")
681 .then(function(path) {
682 expect(path).toBe("xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM
");
688 // BIP44 account field changes address list
689 it('Changes the address list if bip44 account is changed', function(done) {
690 driver.findElement(By.css('#bip44 .account'))
692 driver.findElement(By.css('.phrase'))
693 .sendKeys('abandon abandon ability');
694 driver.sleep(generateDelay).then(function() {
695 getFirstAddress(function(address) {
696 expect(address).toBe("1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H
");
702 // BIP44 change field changes address list
703 it('Changes the address list if bip44 change is changed', function(done) {
704 driver.findElement(By.css('#bip44 .change'))
706 driver.findElement(By.css('.phrase'))
707 .sendKeys('abandon abandon ability');
708 driver.sleep(generateDelay).then(function() {
709 getFirstAddress(function(address) {
710 expect(address).toBe("1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo
");
716 // BIP32 derivation path can be set
717 it('Can use a custom bip32 derivation path', function(done) {
718 driver.findElement(By.css('#bip32-tab a'))
720 driver.findElement(By.css('#bip32 .path'))
722 driver.findElement(By.css('#bip32 .path'))
724 driver.findElement(By.css('.phrase'))
725 .sendKeys('abandon abandon ability');
726 driver.sleep(generateDelay).then(function() {
727 getFirstAddress(function(address) {
728 expect(address).toBe("16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L
");
734 // BIP32 can use hardened derivation paths
735 it('Can use a hardened derivation paths', function(done) {
736 driver.findElement(By.css('#bip32-tab a'))
738 driver.findElement(By.css('#bip32 .path'))
740 driver.findElement(By.css('#bip32 .path'))
742 driver.findElement(By.css('.phrase
'))
743 .sendKeys('abandon abandon ability
');
744 driver.sleep(generateDelay).then(function() {
745 getFirstAddress(function(address) {
746 expect(address).toBe("14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4");
752 // BIP32 extended private key is shown
753 it('Shows the BIP32 extended
private key
', function(done) {
754 driver.findElement(By.css('#bip32
-tab a
'))
756 driver.findElement(By.css('.phrase
'))
757 .sendKeys('abandon abandon ability
');
758 driver.sleep(generateDelay).then(function() {
759 driver.findElement(By.css('.extended
-priv
-key
'))
760 .getAttribute("value")
761 .then(function(privKey) {
762 expect(privKey).toBe("xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe");
768 // BIP32 extended public key is shown
769 it('Shows the BIP32 extended
public key
', function(done) {
770 driver.findElement(By.css('#bip32
-tab a
'))
772 driver.findElement(By.css('.phrase
'))
773 .sendKeys('abandon abandon ability
');
774 driver.sleep(generateDelay).then(function() {
775 driver.findElement(By.css('.extended
-pub
-key
'))
776 .getAttribute("value")
777 .then(function(pubKey) {
778 expect(pubKey).toBe("xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P");
784 // Derivation path is shown in table
785 it('Shows the derivation path
in the table
', function(done) {
786 driver.findElement(By.css('.phrase
'))
787 .sendKeys('abandon abandon ability
');
788 driver.sleep(generateDelay).then(function() {
789 getFirstPath(function(path) {
790 expect(path).toBe("m/44'/0'/0'/0/0");
796 // Derivation path for address can be hardened
797 it('Can derive hardened addresses', function(done) {
798 driver.findElement(By.css('#bip32-tab a'))
800 driver.executeScript(function() {
801 $(".hardened
-addresses
").prop("checked
", true);
803 driver.findElement(By.css('.phrase'))
804 .sendKeys('abandon abandon ability');
805 driver.sleep(generateDelay).then(function() {
806 getFirstAddress(function(address) {
807 expect(address).toBe("18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd
");
813 // Derivation path visibility can be toggled
814 it('Can toggle visibility of the derivation path column', function(done) {
815 driver.findElement(By.css('.phrase'))
816 .sendKeys('abandon abandon ability');
817 driver.sleep(generateDelay).then(function() {
818 driver.findElement(By.css('.index-toggle'))
820 testColumnValuesAreInvisible(done, "index
");
825 it('Shows the address in the table', function(done) {
826 driver.findElement(By.css('.phrase'))
827 .sendKeys('abandon abandon ability');
828 driver.sleep(generateDelay).then(function() {
829 getFirstAddress(function(address) {
830 expect(address).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug
");
836 // Addresses are shown in order of derivation path
837 it('Shows the address in order of derivation path', function(done) {
838 driver.findElement(By.css('.phrase'))
839 .sendKeys('abandon abandon ability');
840 driver.sleep(generateDelay).then(function() {
841 testRowsAreInCorrectOrder(done);
845 // Address visibility can be toggled
846 it('Can toggle visibility of the address column', function(done) {
847 driver.findElement(By.css('.phrase'))
848 .sendKeys('abandon abandon ability');
849 driver.sleep(generateDelay).then(function() {
850 driver.findElement(By.css('.address-toggle'))
852 testColumnValuesAreInvisible(done, "address
");
856 // Public key is shown in table
857 it('Shows the public key in the table', function(done) {
858 driver.findElement(By.css('.phrase'))
859 .sendKeys('abandon abandon ability');
860 driver.sleep(generateDelay).then(function() {
861 driver.findElements(By.css('.pubkey'))
862 .then(function(els) {
864 .then(function(pubkey) {
865 expect(pubkey).toBe("033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3
");
872 // Public key visibility can be toggled
873 it('Can toggle visibility of the public key column', function(done) {
874 driver.findElement(By.css('.phrase'))
875 .sendKeys('abandon abandon ability');
876 driver.sleep(generateDelay).then(function() {
877 driver.findElement(By.css('.public-key-toggle'))
879 testColumnValuesAreInvisible(done, "pubkey
");
883 // Private key is shown in table
884 it('Shows the private key in the table', function(done) {
885 driver.findElement(By.css('.phrase'))
886 .sendKeys('abandon abandon ability');
887 driver.sleep(generateDelay).then(function() {
888 driver.findElements(By.css('.privkey'))
889 .then(function(els) {
891 .then(function(pubkey) {
892 expect(pubkey).toBe("L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE
");
899 // Private key visibility can be toggled
900 it('Can toggle visibility of the private key column', function(done) {
901 driver.findElement(By.css('.phrase'))
902 .sendKeys('abandon abandon ability');
903 driver.sleep(generateDelay).then(function() {
904 driver.findElement(By.css('.private-key-toggle'))
906 testColumnValuesAreInvisible(done, "privkey
");
910 // More addresses can be generated
911 it('Can generate more rows in the table', function(done) {
912 driver.findElement(By.css('.phrase'))
913 .sendKeys('abandon abandon ability');
914 driver.sleep(generateDelay).then(function() {
915 driver.findElement(By.css('.more'))
917 driver.sleep(generateDelay).then(function() {
918 driver.findElements(By.css('.address'))
919 .then(function(els) {
920 expect(els.length).toBe(40);
927 // A custom number of additional addresses can be generated
928 it('Can generate more rows in the table', function(done) {
929 driver.findElement(By.css('.rows-to-add'))
931 driver.findElement(By.css('.rows-to-add'))
933 driver.findElement(By.css('.phrase'))
934 .sendKeys('abandon abandon ability');
935 driver.sleep(generateDelay).then(function() {
936 driver.findElement(By.css('.more'))
938 driver.sleep(generateDelay).then(function() {
939 driver.findElements(By.css('.address'))
940 .then(function(els) {
941 expect(els.length).toBe(21);
948 // Additional addresses are shown in order of derivation path
949 it('Shows additional addresses in order of derivation path', function(done) {
950 driver.findElement(By.css('.phrase'))
951 .sendKeys('abandon abandon ability');
952 driver.sleep(generateDelay).then(function() {
953 driver.findElement(By.css('.more'))
955 driver.sleep(generateDelay).then(function() {
956 testRowsAreInCorrectOrder(done);
961 // BIP32 root key can be set by the user
962 it('Allows the user to set the BIP32 root key', function(done) {
963 driver.findElement(By.css('.root-key'))
964 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi');
965 driver.sleep(generateDelay).then(function() {
966 getFirstAddress(function(address) {
967 expect(address).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug
");
973 // Setting BIP32 root key clears the existing phrase, passphrase and seed
974 // TODO this doesn't work in selenium with chrome
975 it('Confirms the existing phrase should be cleared', function(done) {
976 if (browser == "chrome
") {
977 pending("Selenium
+ Chrome headless bug
for alert
, see
https://stackoverflow.com/q/45242264");
979 driver
.findElement(By
.css('.phrase'))
980 .sendKeys('A non-blank but invalid value');
981 driver
.findElement(By
.css('.root-key'))
982 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi');
983 driver
.switchTo().alert().accept();
984 driver
.findElement(By
.css('.phrase'))
985 .getAttribute("value").then(function(value
) {
986 expect(value
).toBe("");
991 // Clearing of phrase, passphrase and seed can be cancelled by user
992 // TODO this doesn't work in selenium with chrome
993 it('Allows the clearing of the phrase to be cancelled', function(done
) {
994 if (browser
== "chrome") {
995 pending("Selenium + Chrome headless bug for alert, see https://stackoverflow.com/q/45242264");
997 driver
.findElement(By
.css('.phrase'))
998 .sendKeys('abandon abandon ability');
999 driver
.sleep(generateDelay
).then(function() {
1000 driver
.findElement(By
.css('.root-key'))
1002 driver
.findElement(By
.css('.root-key'))
1004 driver
.switchTo().alert().dismiss();
1005 driver
.findElement(By
.css('.phrase'))
1006 .getAttribute("value").then(function(value
) {
1007 expect(value
).toBe("abandon abandon ability");
1013 // Custom BIP32 root key is used when changing the derivation path
1014 it('Can set derivation path for root key instead of phrase', function(done
) {
1015 driver
.findElement(By
.css('#bip44 .account'))
1017 driver
.findElement(By
.css('.root-key'))
1018 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi');
1019 driver
.sleep(generateDelay
).then(function() {
1020 getFirstAddress(function(address
) {
1021 expect(address
).toBe("1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H");
1027 // Incorrect mnemonic shows error
1028 it('Shows an error for incorrect mnemonic', function(done
) {
1029 driver
.findElement(By
.css('.phrase'))
1030 .sendKeys('abandon abandon abandon');
1031 driver
.sleep(feedbackDelay
).then(function() {
1032 driver
.findElement(By
.css('.feedback'))
1034 .then(function(feedback
) {
1035 expect(feedback
).toBe("Invalid mnemonic");
1041 // Incorrect word shows suggested replacement
1042 it('Shows word suggestion for incorrect word', function(done
) {
1043 driver
.findElement(By
.css('.phrase'))
1044 .sendKeys('abandon abandon abiliti');
1045 driver
.sleep(feedbackDelay
).then(function() {
1046 driver
.findElement(By
.css('.feedback'))
1048 .then(function(feedback
) {
1049 var msg
= "abiliti not in wordlist, did you mean ability?";
1050 expect(feedback
).toBe(msg
);
1056 // Github pull request 48
1057 // First four letters of word shows that word, not closest
1058 // since first four letters gives unique word in BIP39 wordlist
1059 // eg ille should show illegal, not idle
1060 it('Shows word suggestion based on first four chars', function(done
) {
1061 driver
.findElement(By
.css('.phrase'))
1063 driver
.sleep(feedbackDelay
).then(function() {
1064 driver
.findElement(By
.css('.feedback'))
1066 .then(function(feedback
) {
1067 var msg
= "ille not in wordlist, did you mean illegal?";
1068 expect(feedback
).toBe(msg
);
1074 // Incorrect BIP32 root key shows error
1075 it('Shows error for incorrect root key', function(done
) {
1076 driver
.findElement(By
.css('.root-key'))
1077 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj');
1078 driver
.sleep(feedbackDelay
).then(function() {
1079 driver
.findElement(By
.css('.feedback'))
1081 .then(function(feedback
) {
1082 var msg
= "Invalid root key";
1083 expect(feedback
).toBe(msg
);
1089 // Derivation path not starting with m shows error
1090 it('Shows error for derivation path not starting with m', function(done
) {
1091 driver
.findElement(By
.css('#bip32-tab a'))
1093 driver
.findElement(By
.css('#bip32 .path'))
1095 driver
.findElement(By
.css('#bip32 .path'))
1097 driver
.findElement(By
.css('.phrase'))
1098 .sendKeys('abandon abandon ability');
1099 driver
.sleep(feedbackDelay
).then(function() {
1100 driver
.findElement(By
.css('.feedback'))
1102 .then(function(feedback
) {
1103 var msg
= "First character must be 'm'";
1104 expect(feedback
).toBe(msg
);
1110 // Derivation path containing invalid characters shows useful error
1111 it('Shows error for derivation path not starting with m', function(done
) {
1112 driver
.findElement(By
.css('#bip32-tab a'))
1114 driver
.findElement(By
.css('#bip32 .path'))
1116 driver
.findElement(By
.css('#bip32 .path'))
1117 .sendKeys('m/1/0wrong1/1');
1118 driver
.findElement(By
.css('.phrase'))
1119 .sendKeys('abandon abandon ability');
1120 driver
.sleep(feedbackDelay
).then(function() {
1121 driver
.findElement(By
.css('.feedback'))
1123 .then(function(feedback
) {
1124 var msg
= "Invalid characters 0wrong1 found at depth 2";
1125 expect(feedback
).toBe(msg
);
1131 // Github Issue 11: Default word length is 15
1132 // https://github.com/iancoleman/bip39/issues/11
1133 it('Sets the default word length to 15', function(done
) {
1134 driver
.findElement(By
.css('.strength'))
1135 .getAttribute("value")
1136 .then(function(strength
) {
1137 expect(strength
).toBe("15");
1142 // Github Issue 12: Generate more rows with private keys hidden
1143 // https://github.com/iancoleman/bip39/issues/12
1144 it('Sets the correct hidden column state on new rows', function(done
) {
1145 driver
.findElement(By
.css('.phrase'))
1146 .sendKeys("abandon abandon ability");
1147 driver
.sleep(generateDelay
).then(function() {
1148 driver
.findElement(By
.css('.private-key-toggle'))
1150 driver
.findElement(By
.css('.more'))
1152 driver
.sleep(generateDelay
).then(function() {
1153 driver
.findElements(By
.css('.privkey'))
1154 .then(function(els
) {
1155 expect(els
.length
).toBe(40);
1157 testColumnValuesAreInvisible(done
, "privkey");
1162 // Github Issue 19: Mnemonic is not sensitive to whitespace
1163 // https://github.com/iancoleman/bip39/issues/19
1164 it('Ignores excess whitespace in the mnemonic', function(done
) {
1165 var doublespace
= " ";
1166 var mnemonic
= "urge cat" + doublespace
+ "bid";
1167 driver
.findElement(By
.css('.phrase'))
1168 .sendKeys(mnemonic
);
1169 driver
.sleep(generateDelay
).then(function() {
1170 driver
.findElement(By
.css('.root-key'))
1171 .getAttribute("value")
1172 .then(function(seed
) {
1173 expect(seed
).toBe("xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC");
1179 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1180 // https://github.com/iancoleman/bip39/issues/23
1181 it('Uses the correct derivation path when changing tabs', function(done
) {
1182 // 1) and 2) set the phrase
1183 driver
.findElement(By
.css('.phrase'))
1184 .sendKeys("abandon abandon ability");
1185 driver
.sleep(generateDelay
).then(function() {
1186 // 3) select bip32 tab
1187 driver
.findElement(By
.css('#bip32-tab a'))
1189 driver
.sleep(generateDelay
).then(function() {
1190 // 4) switch from bitcoin to litecoin
1191 selectNetwork("LTC - Litecoin");
1192 driver
.sleep(generateDelay
).then(function() {
1193 // 5) Check address is displayed correctly
1194 getFirstAddress(function(address
) {
1195 expect(address
).toBe("LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5");
1196 // 5) Check derivation path is displayed correctly
1197 getFirstPath(function(path
) {
1198 expect(path
).toBe("m/0/0");
1207 // Github Issue 23 Part 2: Coin selection in derivation path
1208 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1209 it('Uses the correct derivation path when changing coins', function(done
) {
1211 driver
.findElement(By
.css('.phrase'))
1212 .sendKeys("abandon abandon ability");
1213 driver
.sleep(generateDelay
).then(function() {
1214 // switch from bitcoin to clam
1215 selectNetwork("CLAM - Clams");
1216 driver
.sleep(generateDelay
).then(function() {
1217 // check derivation path is displayed correctly
1218 getFirstPath(function(path
) {
1219 expect(path
).toBe("m/44'/23'/0'/0/0");
1226 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1227 // https://github.com/iancoleman/bip39/issues/26
1228 it('Uses the correct derivation for altcoins with root keys', function(done
) {
1229 // 1) 2) and 3) set the root key
1230 driver
.findElement(By
.css('.root-key'))
1231 .sendKeys("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi");
1232 driver
.sleep(generateDelay
).then(function() {
1233 // 4) switch from bitcoin to viacoin
1234 selectNetwork("VIA - Viacoin");
1235 driver
.sleep(generateDelay
).then(function() {
1236 // 5) ensure the derived address is correct
1237 getFirstAddress(function(address
) {
1238 expect(address
).toBe("Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT");
1245 // Selecting a language with no existing phrase should generate a phrase in
1247 it('Generate a random phrase when language is selected and no current phrase', function(done
) {
1248 driver
.findElement(By
.css("a[href='#japanese']"))
1250 driver
.sleep(generateDelay
).then(function() {
1251 driver
.findElement(By
.css(".phrase"))
1252 .getAttribute("value").then(function(phrase
) {
1253 expect(phrase
.search(/[a-z]/)).toBe(-1);
1254 expect(phrase
.length
).toBeGreaterThan(0);
1260 // Selecting a language with existing phrase should update the phrase to use
1262 it('Updates existing phrases when the language is changed', function(done
) {
1263 driver
.findElement(By
.css(".phrase"))
1264 .sendKeys("abandon abandon ability");
1265 driver
.sleep(generateDelay
).then(function() {
1266 driver
.findElement(By
.css("a[href='#italian']"))
1268 driver
.sleep(generateDelay
).then(function() {
1269 driver
.findElement(By
.css(".phrase"))
1270 .getAttribute("value").then(function(phrase
) {
1271 // Check only the language changes, not the phrase
1272 expect(phrase
).toBe("abaco abaco abbaglio");
1273 getFirstAddress(function(address
) {
1274 // Check the address is correct
1275 expect(address
).toBe("1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV");
1283 // Suggested replacement for erroneous word in non-English language
1284 it('Shows word suggestion for incorrect word in non-English language', function(done
) {
1285 driver
.findElement(By
.css('.phrase'))
1286 .sendKeys('abaco abaco zbbaglio');
1287 driver
.sleep(feedbackDelay
).then(function() {
1288 driver
.findElement(By
.css('.feedback'))
1290 .then(function(feedback
) {
1291 var msg
= "zbbaglio not in wordlist, did you mean abbaglio?";
1292 expect(feedback
).toBe(msg
);
1298 // Japanese word does not break across lines.
1300 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1301 it('Does not break Japanese words across lines', function(done
) {
1302 driver
.findElement(By
.css('.phrase'))
1303 .getCssValue("word-break")
1304 .then(function(value
) {
1305 expect(value
).toBe("keep-all");
1310 // Language can be specified at page load using hash value in url
1311 it('Can set the language from the url hash', function(done
) {
1312 driver
.get(url
+ "#japanese").then(function() {
1313 driver
.findElement(By
.css('.generate')).click();
1314 driver
.sleep(generateDelay
).then(function() {
1315 driver
.findElement(By
.css(".phrase"))
1316 .getAttribute("value").then(function(phrase
) {
1317 expect(phrase
.search(/[a-z]/)).toBe(-1);
1318 expect(phrase
.length
).toBeGreaterThan(0);
1325 // Entropy can be entered by the user
1326 it('Allows entropy to be entered', function(done
) {
1327 driver
.findElement(By
.css('.use-entropy'))
1329 driver
.findElement(By
.css('.entropy'))
1330 .sendKeys('00000000 00000000 00000000 00000000');
1331 driver
.sleep(generateDelay
).then(function() {
1332 driver
.findElement(By
.css(".phrase"))
1333 .getAttribute("value").then(function(phrase
) {
1334 expect(phrase
).toBe("abandon abandon ability");
1335 getFirstAddress(function(address
) {
1336 expect(address
).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug");
1343 // A warning about entropy is shown to the user, with additional information
1344 it('Shows a warning about using entropy', function(done
) {
1345 driver
.findElement(By
.css('.use-entropy'))
1347 driver
.findElement(By
.css('.entropy-container'))
1349 .then(function(containerText
) {
1350 var warning
= "mnemonic may be insecure";
1351 expect(containerText
).toContain(warning
);
1352 driver
.findElement(By
.css('#entropy-notes'))
1353 .findElement(By
.xpath("parent::*"))
1355 .then(function(notesText
) {
1356 var detail
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
1357 expect(notesText
).toContain(detail
);
1363 // The types of entropy available are described to the user
1364 it('Shows the types of entropy available', function(done
) {
1365 driver
.findElement(By
.css('.entropy'))
1366 .getAttribute("placeholder")
1367 .then(function(placeholderText
) {
1376 for (var i
=0; i
<options
.length
; i
++) {
1377 var option
= options
[i
];
1378 expect(placeholderText
).toContain(option
);
1384 // The actual entropy used is shown to the user
1385 it('Shows the actual entropy used', function(done
) {
1386 driver
.findElement(By
.css('.use-entropy'))
1388 driver
.findElement(By
.css('.entropy'))
1389 .sendKeys('Not A Very Good Entropy Source At All');
1390 driver
.sleep(generateDelay
).then(function() {
1391 driver
.findElement(By
.css('.entropy-container'))
1393 .then(function(text
) {
1394 expect(text
).toMatch(/Filtered Entropy
\s
+AedEceAA
/);
1400 // Binary entropy can be entered
1401 it('Allows binary entropy to be entered', function(done
) {
1402 testEntropyType(done
, "01", "binary");
1405 // Base 6 entropy can be entered
1406 it('Allows base 6 entropy to be entered', function(done
) {
1407 testEntropyType(done
, "012345", "base 6");
1410 // Base 6 dice entropy can be entered
1411 it('Allows base 6 dice entropy to be entered', function(done
) {
1412 testEntropyType(done
, "123456", "base 6 (dice)");
1415 // Base 10 entropy can be entered
1416 it('Allows base 10 entropy to be entered', function(done
) {
1417 testEntropyType(done
, "789", "base 10");
1420 // Hexadecimal entropy can be entered
1421 it('Allows hexadecimal entropy to be entered', function(done
) {
1422 testEntropyType(done
, "abcdef", "hexadecimal");
1425 // Dice entropy value is shown as the converted base 6 value
1426 // ie 123456 is converted to 123450
1427 it('Shows dice entropy as base 6', function(done
) {
1428 driver
.findElement(By
.css('.use-entropy'))
1430 driver
.findElement(By
.css('.entropy'))
1431 .sendKeys("123456");
1432 driver
.sleep(generateDelay
).then(function() {
1433 driver
.findElement(By
.css('.entropy-container'))
1435 .then(function(text
) {
1436 expect(text
).toMatch(/Filtered Entropy
\s
+123450/);
1442 // The number of bits of entropy accumulated is shown
1443 it("Shows the number of bits of entropy for 20 bits of binary", function(done
) {
1444 testEntropyBits(done
, "0000 0000 0000 0000 0000", "20");
1446 it("Shows the number of bits of entropy for 1 bit of binary", function(done
) {
1447 testEntropyBits(done
, "0", "1");
1449 it("Shows the number of bits of entropy for 4 bits of binary", function(done
) {
1450 testEntropyBits(done
, "0000", "4");
1452 it("Shows the number of bits of entropy for 1 character of base 6 (dice)", function(done
) {
1453 // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
1454 testEntropyBits(done
, "6", "2");
1456 it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits", function(done
) {
1457 // 7 in base 10 is 111 in base 2, no leading zeros
1458 testEntropyBits(done
, "7", "3");
1460 it("Shows the number of bits of entropy for 1 character of base 10 with 4 bis", function(done
) {
1461 testEntropyBits(done
, "8", "4");
1463 it("Shows the number of bits of entropy for 1 character of hex", function(done
) {
1464 testEntropyBits(done
, "F", "4");
1466 it("Shows the number of bits of entropy for 2 characters of base 10", function(done
) {
1467 testEntropyBits(done
, "29", "6");
1469 it("Shows the number of bits of entropy for 2 characters of hex", function(done
) {
1470 testEntropyBits(done
, "0A", "8");
1472 it("Shows the number of bits of entropy for 2 characters of hex with 3 leading zeros", function(done
) {
1473 // hex is always multiple of 4 bits of entropy
1474 testEntropyBits(done
, "1A", "8");
1476 it("Shows the number of bits of entropy for 2 characters of hex with 2 leading zeros", function(done
) {
1477 testEntropyBits(done
, "2A", "8");
1479 it("Shows the number of bits of entropy for 2 characters of hex with 1 leading zero", function(done
) {
1480 testEntropyBits(done
, "4A", "8");
1482 it("Shows the number of bits of entropy for 2 characters of hex with no leading zeros", function(done
) {
1483 testEntropyBits(done
, "8A", "8");
1485 it("Shows the number of bits of entropy for 2 characters of hex starting with F", function(done
) {
1486 testEntropyBits(done
, "FA", "8");
1488 it("Shows the number of bits of entropy for 4 characters of hex with leading zeros", function(done
) {
1489 testEntropyBits(done
, "000A", "16");
1491 it("Shows the number of bits of entropy for 4 characters of base 6", function(done
) {
1492 testEntropyBits(done
, "5555", "11");
1494 it("Shows the number of bits of entropy for 4 characters of base 6 dice", function(done
) {
1495 // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of
1496 // 2.58 bits, which is 10.32 bits (rounded down to 10 bits)
1497 testEntropyBits(done
, "6666", "10");
1499 it("Shows the number of bits of entropy for 4 charactes of base 10", function(done
) {
1500 // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded
1502 testEntropyBits(done
, "2227", "13");
1504 it("Shows the number of bits of entropy for 4 characters of hex with 2 leading zeros", function(done
) {
1505 testEntropyBits(done
, "222F", "16");
1507 it("Shows the number of bits of entropy for 4 characters of hex starting with F", function(done
) {
1508 testEntropyBits(done
, "FFFF", "16");
1510 it("Shows the number of bits of entropy for 10 characters of base 10", function(done
) {
1511 // 10 events at 3.32 bits per event
1512 testEntropyBits(done
, "0000101017", "33");
1514 it("Shows the number of bits of entropy for a full deck of cards", function(done
) {
1515 // cards are not replaced, so a full deck is not 52^52 entropy which is 296
1516 // bits, it's 52!, which is 225 bits
1517 testEntropyBits(done
, "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225");
1520 it("Shows details about the entered entropy", function(done
) {
1521 testEntropyFeedback(done
,
1525 type: "hexadecimal",
1529 strength: "less than a second",
1533 it("Shows details about the entered entropy", function(done
) {
1534 testEntropyFeedback(done
,
1536 entropy: "AAAAAAAA",
1537 filtered: "AAAAAAAA",
1538 type: "hexadecimal",
1542 strength: "less than a second - Repeats like \"aaa\" are easy to guess",
1546 it("Shows details about the entered entropy", function(done
) {
1547 testEntropyFeedback(done
,
1549 entropy: "AAAAAAAA B",
1550 filtered: "AAAAAAAAB",
1551 type: "hexadecimal",
1555 strength: "less than a second - Repeats like \"aaa\" are easy to guess",
1559 it("Shows details about the entered entropy", function(done
) {
1560 testEntropyFeedback(done
,
1562 entropy: "AAAAAAAA BBBBBBBB",
1563 filtered: "AAAAAAAABBBBBBBB",
1564 type: "hexadecimal",
1568 strength: "less than a second - Repeats like \"aaa\" are easy to guess",
1572 it("Shows details about the entered entropy", function(done
) {
1573 testEntropyFeedback(done
,
1575 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
1576 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
1577 type: "hexadecimal",
1581 strength: "less than a second",
1585 it("Shows details about the entered entropy", function(done
) {
1586 testEntropyFeedback(done
,
1588 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
1589 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
1590 type: "hexadecimal",
1594 strength: "2 minutes",
1598 it("Shows details about the entered entropy", function(done
) {
1599 testEntropyFeedback(done
,
1601 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
1602 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
1603 type: "hexadecimal",
1611 it("Shows details about the entered entropy", function(done
) {
1612 testEntropyFeedback(done
,
1614 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
1615 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
1616 type: "hexadecimal",
1620 strength: "3 years",
1624 it("Shows details about the entered entropy", function(done
) {
1625 testEntropyFeedback(done
,
1627 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
1628 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
1629 type: "hexadecimal",
1633 strength: "centuries",
1637 it("Shows details about the entered entropy", function(done
) {
1638 testEntropyFeedback(done
,
1645 strength: "less than a second",
1649 it("Shows details about the entered entropy", function(done
) {
1650 testEntropyFeedback(done
,
1652 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1653 type: "card (full deck)",
1657 strength: "centuries",
1661 it("Shows details about the entered entropy", function(done
) {
1662 testEntropyFeedback(done
,
1664 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
1665 type: "card (full deck, 1 duplicate: 3d)",
1669 strength: "centuries",
1673 it("Shows details about the entered entropy", function(done
) {
1674 testEntropyFeedback(done
,
1676 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
1677 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
1681 strength: "centuries",
1685 it("Shows details about the entered entropy", function(done
) {
1686 testEntropyFeedback(done
,
1688 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
1689 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
1693 strength: "centuries",
1697 it("Shows details about the entered entropy", function(done
) {
1698 testEntropyFeedback(done
,
1699 // Next test was throwing uncaught error in zxcvbn
1700 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
1702 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1703 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
1707 strength: "centuries",
1711 it("Shows details about the entered entropy", function(done
) {
1712 testEntropyFeedback(done
,
1713 // Case insensitivity to duplicate cards
1716 type: "card (1 duplicate: AS)",
1720 strength: "less than a second",
1724 it("Shows details about the entered entropy", function(done
) {
1725 testEntropyFeedback(done
,
1728 type: "card (1 duplicate: as)",
1732 strength: "less than a second",
1736 it("Shows details about the entered entropy", function(done
) {
1737 testEntropyFeedback(done
,
1738 // Missing cards are detected
1740 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1741 type: "card (1 missing: 9C)",
1745 strength: "centuries",
1749 it("Shows details about the entered entropy", function(done
) {
1750 testEntropyFeedback(done
,
1752 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1753 type: "card (2 missing: 9C 5D)",
1757 strength: "centuries",
1761 it("Shows details about the entered entropy", function(done
) {
1762 testEntropyFeedback(done
,
1764 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1765 type: "card (4 missing: 9C 5D QD...)",
1769 strength: "centuries",
1773 it("Shows details about the entered entropy", function(done
) {
1774 testEntropyFeedback(done
,
1775 // More than six missing cards does not show message
1777 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
1782 strength: "centuries",
1786 it("Shows details about the entered entropy", function(done
) {
1787 testEntropyFeedback(done
,
1788 // Multiple decks of cards increases bits per event
1793 bitsPerEvent: "4.34",
1797 it("Shows details about the entered entropy", function(done
) {
1798 testEntropyFeedback(done
,
1803 bitsPerEvent: "4.80",
1807 it("Shows details about the entered entropy", function(done
) {
1808 testEntropyFeedback(done
,
1813 bitsPerEvent: "5.01",
1817 it("Shows details about the entered entropy", function(done
) {
1818 testEntropyFeedback(done
,
1820 entropy: "3d3d3d3d",
1823 bitsPerEvent: "5.14",
1827 it("Shows details about the entered entropy", function(done
) {
1828 testEntropyFeedback(done
,
1830 entropy: "3d3d3d3d3d",
1833 bitsPerEvent: "5.22",
1837 it("Shows details about the entered entropy", function(done
) {
1838 testEntropyFeedback(done
,
1840 entropy: "3d3d3d3d3d3d",
1843 bitsPerEvent: "5.28",
1847 it("Shows details about the entered entropy", function(done
) {
1848 testEntropyFeedback(done
,
1850 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
1853 bitsPerEvent: "5.59",
1854 strength: 'less than a second - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
1859 // Entropy is truncated from the left
1860 it('Truncates entropy from the left', function(done
) {
1861 // Truncate from left means 0000 is removed from the start
1862 // which gives mnemonic 'avocado zoo zone'
1863 // not 1111 removed from the end
1864 // which gives the mnemonic 'abstract zoo zoo'
1865 var entropy
= "00000000 00000000 00000000 00000000";
1866 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte
1867 driver
.findElement(By
.css('.use-entropy'))
1869 driver
.findElement(By
.css('.entropy'))
1871 driver
.sleep(generateDelay
).then(function() {
1872 driver
.findElement(By
.css(".phrase"))
1873 .getAttribute("value").then(function(phrase
) {
1874 expect(phrase
).toBe("avocado zoo zone");
1880 // Very large entropy results in very long mnemonics
1881 it('Converts very long entropy to very long mnemonics', function(done
) {
1883 for (var i
=0; i
<33; i
++) {
1884 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
1886 driver
.findElement(By
.css('.use-entropy'))
1888 driver
.findElement(By
.css('.entropy'))
1890 driver
.sleep(generateDelay
).then(function() {
1891 driver
.findElement(By
.css(".phrase"))
1892 .getAttribute("value").then(function(phrase
) {
1893 var wordCount
= phrase
.split(/\s+/g).length
;
1894 expect(wordCount
).toBe(99);
1900 // Is compatible with bip32jp entropy
1901 // https://bip32jp.github.io/english/index.html
1903 // Is incompatible with:
1905 it('Is compatible with bip32jp.github.io', function(done
) {
1906 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
1907 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";
1908 driver
.findElement(By
.css('.use-entropy'))
1910 driver
.findElement(By
.css('.entropy'))
1912 driver
.sleep(generateDelay
).then(function() {
1913 driver
.findElement(By
.css(".phrase"))
1914 .getAttribute("value").then(function(phrase
) {
1915 expect(phrase
).toBe(expectedPhrase
);
1921 // Blank entropy does not generate mnemonic or addresses
1922 it('Does not generate mnemonic for blank entropy', function(done
) {
1923 driver
.findElement(By
.css('.use-entropy'))
1925 driver
.findElement(By
.css('.entropy'))
1927 // check there is no mnemonic
1928 driver
.sleep(generateDelay
).then(function() {
1929 driver
.findElement(By
.css(".phrase"))
1930 .getAttribute("value").then(function(phrase
) {
1931 expect(phrase
).toBe("");
1932 // check there is no mnemonic
1933 driver
.findElements(By
.css(".address"))
1934 .then(function(addresses
) {
1935 expect(addresses
.length
).toBe(0);
1936 // Check the feedback says 'blank entropy'
1937 driver
.findElement(By
.css(".feedback"))
1939 .then(function(feedbackText
) {
1940 expect(feedbackText
).toBe("Blank entropy");
1948 // Mnemonic length can be selected even for weak entropy
1949 it('Allows selection of mnemonic length even for weak entropy', function(done
) {
1950 driver
.findElement(By
.css('.use-entropy'))
1952 driver
.executeScript(function() {
1953 $(".mnemonic-length").val("18").trigger("change");
1955 driver
.findElement(By
.css('.entropy'))
1956 .sendKeys("012345");
1957 driver
.sleep(generateDelay
).then(function() {
1958 driver
.findElement(By
.css(".phrase"))
1959 .getAttribute("value").then(function(phrase
) {
1960 var wordCount
= phrase
.split(/\s+/g).length
;
1961 expect(wordCount
).toBe(18);
1968 // https://github.com/iancoleman/bip39/issues/33
1969 // Final cards should contribute entropy
1970 it('Uses as much entropy as possible for the mnemonic', function(done
) {
1971 driver
.findElement(By
.css('.use-entropy'))
1973 driver
.findElement(By
.css('.entropy'))
1974 .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");
1975 driver
.sleep(generateDelay
).then(function() {
1977 driver
.findElement(By
.css(".phrase"))
1978 .getAttribute("value").then(function(originalPhrase
) {
1979 // Set the last 12 cards to be AS
1980 driver
.findElement(By
.css('.entropy'))
1982 driver
.findElement(By
.css('.entropy'))
1983 .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");
1984 driver
.sleep(generateDelay
).then(function() {
1986 driver
.findElement(By
.css(".phrase"))
1987 .getAttribute("value").then(function(newPhrase
) {
1988 expect(originalPhrase
).not
.toEqual(newPhrase
);
1997 // https://github.com/iancoleman/bip39/issues/35
1999 // TODO this doesn't work in selenium with firefox
2000 // see https://stackoverflow.com/q/40360223
2001 it('Shows a qr code on hover for the phrase', function(done
) {
2002 if (browser
== "firefox") {
2003 pending("Selenium + Firefox bug for mouseMove, see https://stackoverflow.com/q/40360223");
2005 // generate a random mnemonic
2006 var generateEl
= driver
.findElement(By
.css('.generate'));
2008 // toggle qr to show (hidden by default)
2009 var phraseEl
= driver
.findElement(By
.css(".phrase"));
2011 var rootKeyEl
= driver
.findElement(By
.css(".root-key"));
2012 driver
.sleep(generateDelay
).then(function() {
2013 // hover over the root key
2014 driver
.actions().mouseMove(rootKeyEl
).perform().then(function() {
2015 // check the qr code shows
2016 driver
.executeScript(function() {
2017 return $(".qr-container").find("canvas").length
> 0;
2019 .then(function(qrShowing
) {
2020 expect(qrShowing
).toBe(true);
2021 // hover away from the phrase
2022 driver
.actions().mouseMove(generateEl
).perform().then(function() {;
2023 // check the qr code hides
2024 driver
.executeScript(function() {
2025 return $(".qr-container").find("canvas").length
== 0;
2027 .then(function(qrHidden
) {
2028 expect(qrHidden
).toBe(true);
2037 // BIP44 account extendend private key is shown
2038 // github issue 37 - compatibility with electrum
2039 it('Shows the bip44 account extended private key', function(done
) {
2040 driver
.findElement(By
.css(".phrase"))
2041 .sendKeys("abandon abandon ability");
2042 driver
.sleep(generateDelay
).then(function() {
2043 driver
.findElement(By
.css("#bip44 .account-xprv"))
2044 .getAttribute("value")
2045 .then(function(xprv
) {
2046 expect(xprv
).toBe("xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ");
2052 // BIP44 account extendend public key is shown
2053 // github issue 37 - compatibility with electrum
2054 it('Shows the bip44 account extended public key', function(done
) {
2055 driver
.findElement(By
.css(".phrase"))
2056 .sendKeys("abandon abandon ability");
2057 driver
.sleep(generateDelay
).then(function() {
2058 driver
.findElement(By
.css("#bip44 .account-xpub"))
2059 .getAttribute("value")
2060 .then(function(xprv
) {
2061 expect(xprv
).toBe("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2068 // BIP32 root key can be set as an xpub
2069 it('Generates addresses from xpub as bip32 root key', function(done
) {
2070 driver
.findElement(By
.css('#bip32-tab a'))
2072 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2073 driver
.findElement(By
.css("#root-key"))
2074 .sendKeys("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2075 driver
.sleep(generateDelay
).then(function() {
2076 // check the addresses are generated
2077 getFirstAddress(function(address
) {
2078 expect(address
).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug");
2079 // check the xprv key is not set
2080 driver
.findElement(By
.css(".extended-priv-key"))
2081 .getAttribute("value")
2082 .then(function(xprv
) {
2083 expect(xprv
).toBe("NA");
2084 // check the private key is not set
2085 driver
.findElements(By
.css(".privkey"))
2086 .then(function(els
) {
2089 .then(function(privkey
) {
2090 expect(xprv
).toBe("NA");
2100 // xpub for bip32 root key will not work with hardened derivation paths
2101 it('Shows error for hardened derivation paths with xpub root key', function(done
) {
2102 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2103 driver
.findElement(By
.css("#root-key"))
2104 .sendKeys("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2105 driver
.sleep(feedbackDelay
).then(function() {
2106 // Check feedback is correct
2107 driver
.findElement(By
.css('.feedback'))
2109 .then(function(feedback
) {
2110 var msg
= "Hardened derivation path is invalid with xpub key";
2111 expect(feedback
).toBe(msg
);
2112 // Check no addresses are shown
2113 driver
.findElements(By
.css('.addresses tr'))
2114 .then(function(rows
) {
2115 expect(rows
.length
).toBe(0);
2123 // no root key shows feedback
2124 it('Shows feedback for no root key', function(done
) {
2125 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2126 driver
.findElement(By
.css('#bip32-tab a'))
2128 driver
.sleep(feedbackDelay
).then(function() {
2129 // Check feedback is correct
2130 driver
.findElement(By
.css('.feedback'))
2132 .then(function(feedback
) {
2133 expect(feedback
).toBe("Invalid root key");
2140 // display error switching tabs while addresses are generating
2141 it('Can change details while old addresses are still being generated', function(done
) {
2142 // Set to generate 199 more addresses.
2143 // This will take a long time allowing a new set of addresses to be
2144 // generated midway through this lot.
2145 // The newly generated addresses should not include any from the old set.
2146 // Any more than 199 will show an alert which needs to be accepted.
2147 driver
.findElement(By
.css('.rows-to-add'))
2149 driver
.findElement(By
.css('.rows-to-add'))
2152 driver
.findElement(By
.css('.phrase'))
2153 .sendKeys("abandon abandon ability");
2154 driver
.sleep(generateDelay
).then(function() {
2155 // generate more addresses
2156 driver
.findElement(By
.css('.more'))
2158 // change tabs which should cancel the previous generating
2159 driver
.findElement(By
.css('#bip32-tab a'))
2161 driver
.sleep(generateDelay
).then(function() {
2162 driver
.findElements(By
.css('.index'))
2163 .then(function(els
) {
2164 // check the derivation paths have the right quantity
2165 expect(els
.length
).toBe(20);
2166 // check the derivation paths are in order
2167 testRowsAreInCorrectOrder(done
);
2174 // padding for binary should give length with multiple of 256
2175 // hashed entropy 1111 is length 252, so requires 4 leading zeros
2176 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
2177 it('Pads hashed entropy with leading zeros', function(done
) {
2178 driver
.findElement(By
.css('.use-entropy'))
2180 driver
.executeScript(function() {
2181 $(".mnemonic-length").val("15").trigger("change");
2183 driver
.findElement(By
.css('.entropy'))
2185 driver
.sleep(generateDelay
).then(function() {
2186 driver
.findElement(By
.css('.phrase'))
2187 .getAttribute("value")
2188 .then(function(phrase
) {
2189 expect(phrase
).toBe("avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear");
2195 // Github pull request 55
2196 // https://github.com/iancoleman/bip39/pull/55
2198 it('Can set the derivation path on bip32 tab for bitcoincore', function(done
) {
2199 testClientSelect(done
, {
2201 bip32path: "m/0'/0'",
2202 useHardenedAddresses: "true",
2205 it('Can set the derivation path on bip32 tab for multibit', function(done
) {
2206 testClientSelect(done
, {
2208 bip32path: "m/0'/0",
2209 useHardenedAddresses: null,
2214 // https://github.com/iancoleman/bip39/issues/58
2215 // bip32 derivation is correct, does not drop leading zeros
2217 // https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846
2218 it('Retains leading zeros for bip32 derivation', function(done
) {
2219 driver
.findElement(By
.css(".phrase"))
2220 .sendKeys("fruit wave dwarf banana earth journey tattoo true farm silk olive fence");
2221 driver
.findElement(By
.css(".passphrase"))
2222 .sendKeys("banana");
2223 driver
.sleep(generateDelay
).then(function() {
2224 getFirstAddress(function(address
) {
2225 // Note that bitcore generates an incorrect address
2226 // 13EuKhffWkBE2KUwcbkbELZb1MpzbimJ3Y
2227 // see the medium.com link above for more details
2228 expect(address
).toBe("17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F");
2235 // Japanese mnemonics generate incorrect bip32 seed
2236 // BIP39 seed is set from phrase
2237 it('Generates correct seed for Japanese mnemonics', function(done
) {
2238 driver
.findElement(By
.css(".phrase"))
2239 .sendKeys("あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら");
2240 driver
.findElement(By
.css(".passphrase"))
2241 .sendKeys("メートルガバヴァぱばぐゞちぢ十人十色");
2242 driver
.sleep(generateDelay
).then(function() {
2243 driver
.findElement(By
.css(".seed"))
2244 .getAttribute("value")
2245 .then(function(seed
) {
2246 expect(seed
).toBe("a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55");
2252 // BIP49 official test vectors
2253 // https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki#test-vectors
2254 it('Generates BIP49 addresses matching the official test vectors', function(done
) {
2255 driver
.findElement(By
.css('#bip49-tab a'))
2257 selectNetwork("BTC - Bitcoin Testnet");
2258 driver
.findElement(By
.css(".phrase"))
2259 .sendKeys("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
2260 driver
.sleep(generateDelay
).then(function() {
2261 getFirstAddress(function(address
) {
2262 expect(address
).toBe("2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2");
2268 // BIP49 derivation path is shown
2269 it('Shows the bip49 derivation path', function(done
) {
2270 driver
.findElement(By
.css('#bip49-tab a'))
2272 driver
.findElement(By
.css(".phrase"))
2273 .sendKeys("abandon abandon ability");
2274 driver
.sleep(generateDelay
).then(function() {
2275 driver
.findElement(By
.css('#bip49 .path'))
2276 .getAttribute("value")
2277 .then(function(path
) {
2278 expect(path
).toBe("m/49'/0'/0'/0");
2284 // BIP49 extended private key is shown
2285 it('Shows the bip49 extended private key', function(done
) {
2286 driver
.findElement(By
.css('#bip49-tab a'))
2288 driver
.findElement(By
.css(".phrase"))
2289 .sendKeys("abandon abandon ability");
2290 driver
.sleep(generateDelay
).then(function() {
2291 driver
.findElement(By
.css('.extended-priv-key'))
2292 .getAttribute("value")
2293 .then(function(xprv
) {
2294 expect(xprv
).toBe("yprvALYB4DYRG6CzzVgzQZwwqjAA2wjBGC3iEd7KYYScpoDdmf75qMRWZWxoFcRXBJjgEXdFqJ9vDRGRLJQsrL22Su5jMbNFeM9vetaGVqy9Qy2");
2300 // BIP49 extended public key is shown
2301 it('Shows the bip49 extended public key', function(done
) {
2302 driver
.findElement(By
.css('#bip49-tab a'))
2304 driver
.findElement(By
.css(".phrase"))
2305 .sendKeys("abandon abandon ability");
2306 driver
.sleep(generateDelay
).then(function() {
2307 driver
.findElement(By
.css('.extended-pub-key'))
2308 .getAttribute("value")
2309 .then(function(xprv
) {
2310 expect(xprv
).toBe("ypub6ZXXTj5K6TmJCymTWbUxCs6tayZffemZbr2vLvrEP8kceTSENtjm7KHH6thvAKxVar9fGe8rgsPEX369zURLZ68b4f7Vexz7RuXsjQ69YDt");
2316 // BIP49 account field changes address list
2317 it('Can set the bip49 account field', function(done
) {
2318 driver
.findElement(By
.css('#bip49-tab a'))
2320 driver
.findElement(By
.css('#bip49 .account'))
2322 driver
.findElement(By
.css('#bip49 .account'))
2324 driver
.findElement(By
.css(".phrase"))
2325 .sendKeys("abandon abandon ability");
2326 driver
.sleep(generateDelay
).then(function() {
2327 getFirstAddress(function(address
) {
2328 expect(address
).toBe("381wg1GGN4rP88rNC9v7QWsiww63yLVPsn");
2334 // BIP49 change field changes address list
2335 it('Can set the bip49 change field', function(done
) {
2336 driver
.findElement(By
.css('#bip49-tab a'))
2338 driver
.findElement(By
.css('#bip49 .change'))
2340 driver
.findElement(By
.css('#bip49 .change'))
2342 driver
.findElement(By
.css(".phrase"))
2343 .sendKeys("abandon abandon ability");
2344 driver
.sleep(generateDelay
).then(function() {
2345 getFirstAddress(function(address
) {
2346 expect(address
).toBe("3PEM7MiKed5konBoN66PQhK8r3hjGhy9dT");
2352 // BIP49 account extendend private key is shown
2353 it('Shows the bip49 account extended private key', function(done
) {
2354 driver
.findElement(By
.css('#bip49-tab a'))
2356 driver
.findElement(By
.css(".phrase"))
2357 .sendKeys("abandon abandon ability");
2358 driver
.sleep(generateDelay
).then(function() {
2359 driver
.findElement(By
.css('#bip49 .account-xprv'))
2360 .getAttribute("value")
2361 .then(function(xprv
) {
2362 expect(xprv
).toBe("yprvAHtB1M5Wp675aLzFy9TJYK2mSsLkg6mcBRh5DZTR7L4EnYSmYPaL63KFA4ycg1PngW5LfkmejxzosCs17TKZMpRFKc3z5SJar6QAKaFcaZL");
2368 // BIP49 account extendend public key is shown
2369 it('Shows the bip49 account extended public key', function(done
) {
2370 driver
.findElement(By
.css('#bip49-tab a'))
2372 driver
.findElement(By
.css(".phrase"))
2373 .sendKeys("abandon abandon ability");
2374 driver
.sleep(generateDelay
).then(function() {
2375 driver
.findElement(By
.css('#bip49 .account-xpub'))
2376 .getAttribute("value")
2377 .then(function(xprv
) {
2378 expect(xprv
).toBe("ypub6WsXQrcQeTfNnq4j5AzJuSyVzuBF5ZVTYecg1ws2ffbDfLmv5vtadqdj1NgR6C6gufMpMfJpHxvb6JEQKvETVNWCRanNedfJtnTchZiJtsL");
2384 // Test selecting coin where bip49 is unavailable (eg CLAM)
2385 it('Shows an error on bip49 tab for coins without bip49', function(done
) {
2386 driver
.findElement(By
.css('#bip49-tab a'))
2388 driver
.findElement(By
.css(".phrase"))
2389 .sendKeys("abandon abandon ability");
2390 driver
.sleep(generateDelay
).then(function() {
2391 selectNetwork("CLAM - Clams");
2392 // bip49 available is hidden
2393 driver
.findElement(By
.css('#bip49 .available'))
2394 .getAttribute("class")
2395 .then(function(classes
) {
2396 expect(classes
).toContain("hidden");
2397 // bip49 unavailable is shown
2398 driver
.findElement(By
.css('#bip49 .unavailable'))
2399 .getAttribute("class")
2400 .then(function(classes
) {
2401 expect(classes
).not
.toContain("hidden");
2402 // check there are no addresses shown
2403 driver
.findElements(By
.css('.addresses tr'))
2404 .then(function(rows
) {
2405 expect(rows
.length
).toBe(0);
2406 // check the derived private key is blank
2407 driver
.findElement(By
.css('.extended-priv-key'))
2408 .getAttribute("value")
2409 .then(function(xprv
) {
2410 expect(xprv
).toBe('');
2411 // check the derived public key is blank
2412 driver
.findElement(By
.css('.extended-pub-key'))
2413 .getAttribute("value")
2414 .then(function(xpub
) {
2415 expect(xpub
).toBe('');
2426 // Cleared mnemonic and root key still allows addresses to be generated
2427 // https://github.com/iancoleman/bip39/issues/43
2428 it('Clears old root keys from memory when mnemonic is cleared', function(done
) {
2430 driver
.findElement(By
.css(".phrase"))
2431 .sendKeys("abandon abandon ability");
2432 driver
.sleep(generateDelay
).then(function() {
2433 // clear the mnemonic and root key
2434 // using selenium .clear() doesn't seem to trigger the 'input' event
2435 // so clear it using keys instead
2436 driver
.findElement(By
.css('.phrase'))
2437 .sendKeys(Key
.CONTROL
,"a");
2438 driver
.findElement(By
.css('.phrase'))
2439 .sendKeys(Key
.DELETE
);
2440 driver
.findElement(By
.css('.root-key'))
2441 .sendKeys(Key
.CONTROL
,"a");
2442 driver
.findElement(By
.css('.root-key'))
2443 .sendKeys(Key
.DELETE
);
2444 driver
.sleep(generateDelay
).then(function() {
2445 // try to generate more addresses
2446 driver
.findElement(By
.css('.more'))
2448 driver
.sleep(generateDelay
).then(function() {
2449 driver
.findElements(By
.css(".addresses tr"))
2450 .then(function(els
) {
2451 // check there are no addresses shown
2452 expect(els
.length
).toBe(0);
2461 // error trying to generate addresses from xpub with hardened derivation
2462 it('Shows error for hardened addresses with xpub root key', function(done
) {
2463 driver
.findElement(By
.css('#bip32-tab a'))
2465 driver
.executeScript(function() {
2466 $(".hardened-addresses").prop("checked", true);
2468 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2469 driver
.findElement(By
.css("#root-key"))
2470 .sendKeys("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2471 driver
.sleep(feedbackDelay
).then(function() {
2472 // Check feedback is correct
2473 driver
.findElement(By
.css('.feedback'))
2475 .then(function(feedback
) {
2476 var msg
= "Hardened derivation path is invalid with xpub key";
2477 expect(feedback
).toBe(msg
);
2483 // Litecoin uses ltub by default, and can optionally be set to xprv
2485 // https://github.com/iancoleman/bip39/issues/96
2486 // Issue with extended keys on Litecoin
2487 it('Uses ltub by default for litecoin, but can be set to xprv', function(done
) {
2488 driver
.findElement(By
.css('.phrase'))
2489 .sendKeys("abandon abandon ability");
2490 selectNetwork("LTC - Litecoin");
2491 driver
.sleep(generateDelay
).then(function() {
2492 // check the extended key is generated correctly
2493 driver
.findElement(By
.css('.root-key'))
2494 .getAttribute("value")
2495 .then(function(rootKey
) {
2496 expect(rootKey
).toBe("Ltpv71G8qDifUiNesiPqf6h5V6eQ8ic77oxQiYtawiACjBEx3sTXNR2HGDGnHETYxESjqkMLFBkKhWVq67ey1B2MKQXannUqNy1RZVHbmrEjnEU");
2497 // set litecoin to use ltub
2498 driver
.executeScript(function() {
2499 $(".litecoin-use-ltub").prop("checked", false);
2500 $(".litecoin-use-ltub").trigger("change");
2502 driver
.sleep(generateDelay
).then(function() {
2503 driver
.findElement(By
.css('.root-key'))
2504 .getAttribute("value")
2505 .then(function(rootKey
) {
2506 expect(rootKey
).toBe("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi");
2515 // https://github.com/iancoleman/bip39/issues/99#issuecomment-327094159
2516 // "warn me emphatically when they have detected invalid input" to the entropy field
2517 // A warning is shown when entropy is filtered and discarded
2518 it('Warns when entropy is filtered and discarded', function(done
) {
2519 driver
.findElement(By
.css('.use-entropy'))
2521 // set entropy to have no filtered content
2522 driver
.findElement(By
.css('.entropy'))
2523 .sendKeys("00000000 00000000 00000000 00000000");
2524 driver
.sleep(generateDelay
).then(function() {
2525 // check the filter warning does not show
2526 driver
.findElement(By
.css('.entropy-container .filter-warning'))
2527 .getAttribute("class")
2528 .then(function(classes
) {
2529 expect(classes
).toContain("hidden");
2530 // set entropy to have some filtered content
2531 driver
.findElement(By
.css('.entropy'))
2532 .sendKeys("10000000 zxcvbn 00000000 00000000 00000000");
2533 driver
.sleep(entropyFeedbackDelay
).then(function() {
2534 // check the filter warning shows
2535 driver
.findElement(By
.css('.entropy-container .filter-warning'))
2536 .getAttribute("class")
2537 .then(function(classes
) {
2538 expect(classes
).not
.toContain("hidden");
2546 // Bitcoin Cash address can be set to use bitpay format
2547 it('Can use bitpay format for bitcoin cash addresses', function(done
) {
2548 driver
.executeScript(function() {
2549 $(".use-bitpay-addresses").prop("checked", true);
2551 driver
.findElement(By
.css('.phrase'))
2552 .sendKeys("abandon abandon ability");
2553 selectNetwork("BCH - Bitcoin Cash");
2554 driver
.sleep(generateDelay
).then(function() {
2555 getFirstAddress(function(address
) {
2556 expect(address
).toBe("CZnpA9HPmvhuhLLPWJP8rNDpLUYXy1LXFk");
2562 // End of tests ported from old suit, so no more comments above each test now
2564 it('Can generate more addresses from a custom index', function(done
) {
2565 var expectedIndexes
= [
2566 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
2567 40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59
2569 driver
.findElement(By
.css('.phrase'))
2570 .sendKeys("abandon abandon ability");
2571 driver
.sleep(generateDelay
).then(function() {
2572 // Set start of next lot of rows to be from index 40
2573 // which means indexes 20-39 will not be in the table.
2574 driver
.findElement(By
.css('.more-rows-start-index'))
2576 driver
.findElement(By
.css('.more'))
2578 driver
.sleep(generateDelay
).then(function() {
2579 // Check actual indexes in the table match the expected pattern
2580 driver
.findElements(By
.css(".index"))
2581 .then(function(els
) {
2582 expect(els
.length
).toBe(expectedIndexes
.length
);
2583 var testRowAtIndex = function(i
) {
2584 if (i
>= expectedIndexes
.length
) {
2589 .then(function(actualPath
) {
2590 var noHardened
= actualPath
.replace(/'/g, "");
2591 var pathBits = noHardened.split("/")
2592 var lastBit = pathBits[pathBits.length-1];
2593 var actualIndex = parseInt(lastBit);
2594 var expectedIndex = expectedIndexes[i];
2595 expect(actualIndex).toBe(expectedIndex);
2596 testRowAtIndex(i+1);
2606 it('Can generate BIP141 addresses
with P2WPKH
-in-P2SH semanitcs
', function(done) {
2607 // Sourced from BIP49 official test specs
2608 driver.findElement(By.css('#bip141
-tab a
'))
2610 driver.findElement(By.css('.bip141
-path
'))
2612 driver.findElement(By.css('.bip141
-path
'))
2613 .sendKeys("m/49'/1'/0'/0");
2614 selectNetwork("BTC
- Bitcoin Testnet
");
2615 driver.findElement(By.css(".phrase
"))
2616 .sendKeys("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
");
2617 driver.sleep(generateDelay).then(function() {
2618 getFirstAddress(function(address) {
2619 expect(address).toBe("2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2
");
2625 it('Can generate BIP141 addresses with P2WPKH semanitcs', function(done) {
2626 // This result tested against bitcoinjs-lib test spec for segwit address
2627 // using the first private key of this mnemonic and default path m/0
2628 // https://github.com/bitcoinjs/bitcoinjs-lib/blob/9c8503cab0c6c30a95127042703bc18e8d28c76d/test/integration/addresses.js#L50
2629 // so whilst not directly comparable, substituting the private key produces
2630 // identical results between this tool and the bitcoinjs-lib test.
2631 // Private key generated is:
2632 // L3L8Nu9whawPBNLGtFqDhKut9DKKfG3CQoysupT7BimqVCZsLFNP
2633 driver.findElement(By.css('#bip141-tab a'))
2636 driver.executeScript(function() {
2637 $(".bip141
-semantics option
[selected
]").removeAttr("selected
");
2638 $(".bip141
-semantics option
").filter(function(i,e) {
2639 return $(e).html() == "P2WPKH
";
2640 }).prop("selected
", true);
2641 $(".bip141
-semantics
").trigger("change
");
2643 driver.findElement(By.css(".phrase
"))
2644 .sendKeys("abandon abandon ability
");
2645 driver.sleep(generateDelay).then(function() {
2646 getFirstAddress(function(address) {
2647 expect(address).toBe("bc1qfwu6a5a3evygrk8zvdxxvz4547lmpyx5vsfxe9
");
2653 it('Shows the entropy used by the PRNG when clicking generate', function(done) {
2654 driver.findElement(By.css('.generate')).click();
2655 driver.sleep(generateDelay).then(function() {
2656 driver.findElement(By.css('.entropy'))
2657 .getAttribute("value
")
2658 .then(function(entropy) {
2659 expect(entropy).not.toBe("");
2665 it('Shows the index of each word in the mnemonic', function(done) {
2666 driver.findElement(By.css('.phrase'))
2667 .sendKeys("abandon abandon ability
");
2668 driver.sleep(generateDelay).then(function() {
2669 driver.findElement(By.css('.use-entropy'))
2671 driver.findElement(By.css('.word-indexes'))
2673 .then(function(indexes) {
2674 expect(indexes).toBe("0, 0, 1");