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 namecoin
', function(done) {
497 selectText: "NMC - Namecoin",
498 firstAddress: "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2",
500 testNetwork(done, params);
502 it('Allows selection
of peercoin
', function(done) {
504 selectText: "PPC - Peercoin",
505 firstAddress: "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm",
507 testNetwork(done, params);
509 it('Allows selection
of ethereum
', function(done) {
511 selectText: "ETH - Ethereum",
512 firstAddress: "0xe5815d5902Ad612d49283DEdEc02100Bd44C2772",
514 testNetwork(done, params);
515 // TODO test private key and public key
517 it('Allows selection
of slimcoin
', function(done) {
519 selectText: "SLM - Slimcoin",
520 firstAddress: "SNzPi1CafHFm3WWjRo43aMgiaEEj3ogjww",
522 testNetwork(done, params);
524 it('Allows selection
of slimcoin testnet
', function(done) {
526 selectText: "SLM - Slimcoin Testnet",
527 firstAddress: "n3nMgWufTek5QQAr6uwMhg5xbzj8xqc4Dq",
529 testNetwork(done, params);
531 it('Allows selection
of bitcoin cash
', function(done) {
533 selectText: "BCH - Bitcoin Cash",
534 firstAddress: "1JKvb6wKtsjNoCRxpZ4DGrbniML7z5U16A",
536 testNetwork(done, params);
538 it('Allows selection
of myriadcoin
', function(done) {
540 selectText: "XMY - Myriadcoin",
541 firstAddress: "MJEswvRR46wh9BoiVj9DzKYMBkCramhoBV",
543 testNetwork(done, params);
545 it('Allows selection
of pivx
', function(done) {
547 selectText: "PIVX - PIVX",
548 firstAddress: "DBxgT7faCuno7jmtKuu6KWCiwqsVPqh1tS",
550 testNetwork(done, params);
552 it('Allows selection
of pivx testnet
', function(done) {
554 selectText: "PIVX - PIVX Testnet",
555 firstAddress: "yB5U384n6dGkVE3by5y9VdvHHPwPg68fQj",
557 testNetwork(done, params);
559 it('Allows selection
of maza
', function(done) {
561 selectText: "MAZA - Maza",
562 firstAddress: "MGW4Bmi2NEm4PxSjgeFwhP9vg18JHoRnfw",
564 testNetwork(done, params);
566 it('Allows selection
of fujicoin
', function(done) {
568 selectText: "FJC - Fujicoin",
569 firstAddress: "FgiaLpG7C99DyR4WnPxXedRVHXSfKzUDhF",
571 testNetwork(done, params);
573 it('Allows selection
of nubits
', function(done) {
575 selectText: "USNBT - NuBits",
576 firstAddress: "BLxkabXuZSJSdesLD7KxZdqovd4YwyBTU6",
578 testNetwork(done, params);
580 it('Allows selection
of bitcoin gold
', function(done) {
582 selectText: "BTG - Bitcoin Gold",
583 firstAddress: "GWYxuwSqANWGV3WT7Gpr6HE91euYXBqtwQ",
585 testNetwork(done, params);
588 // BIP39 seed is set from phrase
589 it('Sets the bip39 seed
from the prhase
', function(done) {
590 driver.findElement(By.css('.phrase
'))
591 .sendKeys('abandon abandon ability
');
592 driver.sleep(generateDelay).then(function() {
593 driver.findElement(By.css('.seed
'))
594 .getAttribute("value")
595 .then(function(seed) {
596 expect(seed).toBe("20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3");
602 // BIP32 root key is set from phrase
603 it('Sets the bip39 root key
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('.root
-key
'))
608 .getAttribute("value")
609 .then(function(seed) {
610 expect(seed).toBe("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi");
616 // Tabs show correct addresses when changed
617 it('Shows the correct address when tab is changed
', function(done) {
618 driver.findElement(By.css('.phrase
'))
619 .sendKeys('abandon abandon ability
');
620 driver.sleep(generateDelay).then(function() {
621 driver.findElement(By.css('#bip32
-tab a
'))
623 driver.sleep(generateDelay).then(function() {
624 getFirstAddress(function(address) {
625 expect(address).toBe("17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz");
632 // BIP44 derivation path is shown
633 it('Shows the derivation path
for bip44 tab
', function(done) {
634 driver.findElement(By.css('.phrase
'))
635 .sendKeys('abandon abandon ability
');
636 driver.sleep(generateDelay).then(function() {
637 driver.findElement(By.css('#bip44
.path
'))
638 .getAttribute("value")
639 .then(function(path) {
640 expect(path).toBe("m/44'/0'/0'/0");
646 // BIP44 extended private key is shown
647 it('Shows the extended private key 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('.extended-priv-key'))
652 .getAttribute("value
")
653 .then(function(path) {
654 expect(path).toBe("xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG
");
660 // BIP44 extended public key is shown
661 it('Shows the extended public 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-pub-key'))
666 .getAttribute("value
")
667 .then(function(path) {
668 expect(path).toBe("xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM
");
674 // BIP44 account field changes address list
675 it('Changes the address list if bip44 account is changed', function(done) {
676 driver.findElement(By.css('#bip44 .account'))
678 driver.findElement(By.css('.phrase'))
679 .sendKeys('abandon abandon ability');
680 driver.sleep(generateDelay).then(function() {
681 getFirstAddress(function(address) {
682 expect(address).toBe("1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H
");
688 // BIP44 change field changes address list
689 it('Changes the address list if bip44 change is changed', function(done) {
690 driver.findElement(By.css('#bip44 .change'))
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("1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo
");
702 // BIP32 derivation path can be set
703 it('Can use a custom bip32 derivation path', function(done) {
704 driver.findElement(By.css('#bip32-tab a'))
706 driver.findElement(By.css('#bip32 .path'))
708 driver.findElement(By.css('#bip32 .path'))
710 driver.findElement(By.css('.phrase'))
711 .sendKeys('abandon abandon ability');
712 driver.sleep(generateDelay).then(function() {
713 getFirstAddress(function(address) {
714 expect(address).toBe("16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L
");
720 // BIP32 can use hardened derivation paths
721 it('Can use a hardened derivation paths', function(done) {
722 driver.findElement(By.css('#bip32-tab a'))
724 driver.findElement(By.css('#bip32 .path'))
726 driver.findElement(By.css('#bip32 .path'))
728 driver.findElement(By.css('.phrase
'))
729 .sendKeys('abandon abandon ability
');
730 driver.sleep(generateDelay).then(function() {
731 getFirstAddress(function(address) {
732 expect(address).toBe("14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4");
738 // BIP32 extended private key is shown
739 it('Shows the BIP32 extended
private key
', function(done) {
740 driver.findElement(By.css('#bip32
-tab a
'))
742 driver.findElement(By.css('.phrase
'))
743 .sendKeys('abandon abandon ability
');
744 driver.sleep(generateDelay).then(function() {
745 driver.findElement(By.css('.extended
-priv
-key
'))
746 .getAttribute("value")
747 .then(function(privKey) {
748 expect(privKey).toBe("xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe");
754 // BIP32 extended public key is shown
755 it('Shows the BIP32 extended
public key
', function(done) {
756 driver.findElement(By.css('#bip32
-tab a
'))
758 driver.findElement(By.css('.phrase
'))
759 .sendKeys('abandon abandon ability
');
760 driver.sleep(generateDelay).then(function() {
761 driver.findElement(By.css('.extended
-pub
-key
'))
762 .getAttribute("value")
763 .then(function(pubKey) {
764 expect(pubKey).toBe("xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P");
770 // Derivation path is shown in table
771 it('Shows the derivation path
in the table
', function(done) {
772 driver.findElement(By.css('.phrase
'))
773 .sendKeys('abandon abandon ability
');
774 driver.sleep(generateDelay).then(function() {
775 getFirstPath(function(path) {
776 expect(path).toBe("m/44'/0'/0'/0/0");
782 // Derivation path for address can be hardened
783 it('Can derive hardened addresses', function(done) {
784 driver.findElement(By.css('#bip32-tab a'))
786 driver.executeScript(function() {
787 $(".hardened
-addresses
").prop("checked
", true);
789 driver.findElement(By.css('.phrase'))
790 .sendKeys('abandon abandon ability');
791 driver.sleep(generateDelay).then(function() {
792 getFirstAddress(function(address) {
793 expect(address).toBe("18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd
");
799 // Derivation path visibility can be toggled
800 it('Can toggle visibility of the derivation path column', function(done) {
801 driver.findElement(By.css('.phrase'))
802 .sendKeys('abandon abandon ability');
803 driver.sleep(generateDelay).then(function() {
804 driver.findElement(By.css('.index-toggle'))
806 testColumnValuesAreInvisible(done, "index
");
811 it('Shows the address in the table', function(done) {
812 driver.findElement(By.css('.phrase'))
813 .sendKeys('abandon abandon ability');
814 driver.sleep(generateDelay).then(function() {
815 getFirstAddress(function(address) {
816 expect(address).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug
");
822 // Addresses are shown in order of derivation path
823 it('Shows the address in order of derivation path', function(done) {
824 driver.findElement(By.css('.phrase'))
825 .sendKeys('abandon abandon ability');
826 driver.sleep(generateDelay).then(function() {
827 testRowsAreInCorrectOrder(done);
831 // Address visibility can be toggled
832 it('Can toggle visibility of the address column', function(done) {
833 driver.findElement(By.css('.phrase'))
834 .sendKeys('abandon abandon ability');
835 driver.sleep(generateDelay).then(function() {
836 driver.findElement(By.css('.address-toggle'))
838 testColumnValuesAreInvisible(done, "address
");
842 // Public key is shown in table
843 it('Shows the public key in the table', function(done) {
844 driver.findElement(By.css('.phrase'))
845 .sendKeys('abandon abandon ability');
846 driver.sleep(generateDelay).then(function() {
847 driver.findElements(By.css('.pubkey'))
848 .then(function(els) {
850 .then(function(pubkey) {
851 expect(pubkey).toBe("033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3
");
858 // Public key visibility can be toggled
859 it('Can toggle visibility of the public key column', function(done) {
860 driver.findElement(By.css('.phrase'))
861 .sendKeys('abandon abandon ability');
862 driver.sleep(generateDelay).then(function() {
863 driver.findElement(By.css('.public-key-toggle'))
865 testColumnValuesAreInvisible(done, "pubkey
");
869 // Private key is shown in table
870 it('Shows the private key in the table', function(done) {
871 driver.findElement(By.css('.phrase'))
872 .sendKeys('abandon abandon ability');
873 driver.sleep(generateDelay).then(function() {
874 driver.findElements(By.css('.privkey'))
875 .then(function(els) {
877 .then(function(pubkey) {
878 expect(pubkey).toBe("L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE
");
885 // Private key visibility can be toggled
886 it('Can toggle visibility of the private key column', function(done) {
887 driver.findElement(By.css('.phrase'))
888 .sendKeys('abandon abandon ability');
889 driver.sleep(generateDelay).then(function() {
890 driver.findElement(By.css('.private-key-toggle'))
892 testColumnValuesAreInvisible(done, "privkey
");
896 // More addresses can be generated
897 it('Can generate more rows in the table', function(done) {
898 driver.findElement(By.css('.phrase'))
899 .sendKeys('abandon abandon ability');
900 driver.sleep(generateDelay).then(function() {
901 driver.findElement(By.css('.more'))
903 driver.sleep(generateDelay).then(function() {
904 driver.findElements(By.css('.address'))
905 .then(function(els) {
906 expect(els.length).toBe(40);
913 // A custom number of additional addresses can be generated
914 it('Can generate more rows in the table', function(done) {
915 driver.findElement(By.css('.rows-to-add'))
917 driver.findElement(By.css('.rows-to-add'))
919 driver.findElement(By.css('.phrase'))
920 .sendKeys('abandon abandon ability');
921 driver.sleep(generateDelay).then(function() {
922 driver.findElement(By.css('.more'))
924 driver.sleep(generateDelay).then(function() {
925 driver.findElements(By.css('.address'))
926 .then(function(els) {
927 expect(els.length).toBe(21);
934 // Additional addresses are shown in order of derivation path
935 it('Shows additional addresses in order of derivation path', function(done) {
936 driver.findElement(By.css('.phrase'))
937 .sendKeys('abandon abandon ability');
938 driver.sleep(generateDelay).then(function() {
939 driver.findElement(By.css('.more'))
941 driver.sleep(generateDelay).then(function() {
942 testRowsAreInCorrectOrder(done);
947 // BIP32 root key can be set by the user
948 it('Allows the user to set the BIP32 root key', function(done) {
949 driver.findElement(By.css('.root-key'))
950 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi');
951 driver.sleep(generateDelay).then(function() {
952 getFirstAddress(function(address) {
953 expect(address).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug
");
959 // Setting BIP32 root key clears the existing phrase, passphrase and seed
960 // TODO this doesn't work in selenium with chrome
961 it('Confirms the existing phrase should be cleared', function(done) {
962 if (browser == "chrome
") {
963 pending("Selenium
+ Chrome headless bug
for alert
, see
https://stackoverflow.com/q/45242264");
965 driver
.findElement(By
.css('.phrase'))
966 .sendKeys('A non-blank but invalid value');
967 driver
.findElement(By
.css('.root-key'))
968 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi');
969 driver
.switchTo().alert().accept();
970 driver
.findElement(By
.css('.phrase'))
971 .getAttribute("value").then(function(value
) {
972 expect(value
).toBe("");
977 // Clearing of phrase, passphrase and seed can be cancelled by user
978 // TODO this doesn't work in selenium with chrome
979 it('Allows the clearing of the phrase to be cancelled', function(done
) {
980 if (browser
== "chrome") {
981 pending("Selenium + Chrome headless bug for alert, see https://stackoverflow.com/q/45242264");
983 driver
.findElement(By
.css('.phrase'))
984 .sendKeys('abandon abandon ability');
985 driver
.sleep(generateDelay
).then(function() {
986 driver
.findElement(By
.css('.root-key'))
988 driver
.findElement(By
.css('.root-key'))
990 driver
.switchTo().alert().dismiss();
991 driver
.findElement(By
.css('.phrase'))
992 .getAttribute("value").then(function(value
) {
993 expect(value
).toBe("abandon abandon ability");
999 // Custom BIP32 root key is used when changing the derivation path
1000 it('Can set derivation path for root key instead of phrase', function(done
) {
1001 driver
.findElement(By
.css('#bip44 .account'))
1003 driver
.findElement(By
.css('.root-key'))
1004 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi');
1005 driver
.sleep(generateDelay
).then(function() {
1006 getFirstAddress(function(address
) {
1007 expect(address
).toBe("1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H");
1013 // Incorrect mnemonic shows error
1014 it('Shows an error for incorrect mnemonic', function(done
) {
1015 driver
.findElement(By
.css('.phrase'))
1016 .sendKeys('abandon abandon abandon');
1017 driver
.sleep(feedbackDelay
).then(function() {
1018 driver
.findElement(By
.css('.feedback'))
1020 .then(function(feedback
) {
1021 expect(feedback
).toBe("Invalid mnemonic");
1027 // Incorrect word shows suggested replacement
1028 it('Shows word suggestion for incorrect word', function(done
) {
1029 driver
.findElement(By
.css('.phrase'))
1030 .sendKeys('abandon abandon abiliti');
1031 driver
.sleep(feedbackDelay
).then(function() {
1032 driver
.findElement(By
.css('.feedback'))
1034 .then(function(feedback
) {
1035 var msg
= "abiliti not in wordlist, did you mean ability?";
1036 expect(feedback
).toBe(msg
);
1042 // Github pull request 48
1043 // First four letters of word shows that word, not closest
1044 // since first four letters gives unique word in BIP39 wordlist
1045 // eg ille should show illegal, not idle
1046 it('Shows word suggestion based on first four chars', function(done
) {
1047 driver
.findElement(By
.css('.phrase'))
1049 driver
.sleep(feedbackDelay
).then(function() {
1050 driver
.findElement(By
.css('.feedback'))
1052 .then(function(feedback
) {
1053 var msg
= "ille not in wordlist, did you mean illegal?";
1054 expect(feedback
).toBe(msg
);
1060 // Incorrect BIP32 root key shows error
1061 it('Shows error for incorrect root key', function(done
) {
1062 driver
.findElement(By
.css('.root-key'))
1063 .sendKeys('xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj');
1064 driver
.sleep(feedbackDelay
).then(function() {
1065 driver
.findElement(By
.css('.feedback'))
1067 .then(function(feedback
) {
1068 var msg
= "Invalid root key";
1069 expect(feedback
).toBe(msg
);
1075 // Derivation path not starting with m shows error
1076 it('Shows error for derivation path not starting with m', function(done
) {
1077 driver
.findElement(By
.css('#bip32-tab a'))
1079 driver
.findElement(By
.css('#bip32 .path'))
1081 driver
.findElement(By
.css('#bip32 .path'))
1083 driver
.findElement(By
.css('.phrase'))
1084 .sendKeys('abandon abandon ability');
1085 driver
.sleep(feedbackDelay
).then(function() {
1086 driver
.findElement(By
.css('.feedback'))
1088 .then(function(feedback
) {
1089 var msg
= "First character must be 'm'";
1090 expect(feedback
).toBe(msg
);
1096 // Derivation path containing invalid characters shows useful error
1097 it('Shows error for derivation path not starting with m', function(done
) {
1098 driver
.findElement(By
.css('#bip32-tab a'))
1100 driver
.findElement(By
.css('#bip32 .path'))
1102 driver
.findElement(By
.css('#bip32 .path'))
1103 .sendKeys('m/1/0wrong1/1');
1104 driver
.findElement(By
.css('.phrase'))
1105 .sendKeys('abandon abandon ability');
1106 driver
.sleep(feedbackDelay
).then(function() {
1107 driver
.findElement(By
.css('.feedback'))
1109 .then(function(feedback
) {
1110 var msg
= "Invalid characters 0wrong1 found at depth 2";
1111 expect(feedback
).toBe(msg
);
1117 // Github Issue 11: Default word length is 15
1118 // https://github.com/iancoleman/bip39/issues/11
1119 it('Sets the default word length to 15', function(done
) {
1120 driver
.findElement(By
.css('.strength'))
1121 .getAttribute("value")
1122 .then(function(strength
) {
1123 expect(strength
).toBe("15");
1128 // Github Issue 12: Generate more rows with private keys hidden
1129 // https://github.com/iancoleman/bip39/issues/12
1130 it('Sets the correct hidden column state on new rows', function(done
) {
1131 driver
.findElement(By
.css('.phrase'))
1132 .sendKeys("abandon abandon ability");
1133 driver
.sleep(generateDelay
).then(function() {
1134 driver
.findElement(By
.css('.private-key-toggle'))
1136 driver
.findElement(By
.css('.more'))
1138 driver
.sleep(generateDelay
).then(function() {
1139 driver
.findElements(By
.css('.privkey'))
1140 .then(function(els
) {
1141 expect(els
.length
).toBe(40);
1143 testColumnValuesAreInvisible(done
, "privkey");
1148 // Github Issue 19: Mnemonic is not sensitive to whitespace
1149 // https://github.com/iancoleman/bip39/issues/19
1150 it('Ignores excess whitespace in the mnemonic', function(done
) {
1151 var doublespace
= " ";
1152 var mnemonic
= "urge cat" + doublespace
+ "bid";
1153 driver
.findElement(By
.css('.phrase'))
1154 .sendKeys(mnemonic
);
1155 driver
.sleep(generateDelay
).then(function() {
1156 driver
.findElement(By
.css('.root-key'))
1157 .getAttribute("value")
1158 .then(function(seed
) {
1159 expect(seed
).toBe("xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC");
1165 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1166 // https://github.com/iancoleman/bip39/issues/23
1167 it('Uses the correct derivation path when changing tabs', function(done
) {
1168 // 1) and 2) set the phrase
1169 driver
.findElement(By
.css('.phrase'))
1170 .sendKeys("abandon abandon ability");
1171 driver
.sleep(generateDelay
).then(function() {
1172 // 3) select bip32 tab
1173 driver
.findElement(By
.css('#bip32-tab a'))
1175 driver
.sleep(generateDelay
).then(function() {
1176 // 4) switch from bitcoin to litecoin
1177 selectNetwork("LTC - Litecoin");
1178 driver
.sleep(generateDelay
).then(function() {
1179 // 5) Check address is displayed correctly
1180 getFirstAddress(function(address
) {
1181 expect(address
).toBe("LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5");
1182 // 5) Check derivation path is displayed correctly
1183 getFirstPath(function(path
) {
1184 expect(path
).toBe("m/0/0");
1193 // Github Issue 23 Part 2: Coin selection in derivation path
1194 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1195 it('Uses the correct derivation path when changing coins', function(done
) {
1197 driver
.findElement(By
.css('.phrase'))
1198 .sendKeys("abandon abandon ability");
1199 driver
.sleep(generateDelay
).then(function() {
1200 // switch from bitcoin to clam
1201 selectNetwork("CLAM - Clams");
1202 driver
.sleep(generateDelay
).then(function() {
1203 // check derivation path is displayed correctly
1204 getFirstPath(function(path
) {
1205 expect(path
).toBe("m/44'/23'/0'/0/0");
1212 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1213 // https://github.com/iancoleman/bip39/issues/26
1214 it('Uses the correct derivation for altcoins with root keys', function(done
) {
1215 // 1) 2) and 3) set the root key
1216 driver
.findElement(By
.css('.root-key'))
1217 .sendKeys("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi");
1218 driver
.sleep(generateDelay
).then(function() {
1219 // 4) switch from bitcoin to viacoin
1220 selectNetwork("VIA - Viacoin");
1221 driver
.sleep(generateDelay
).then(function() {
1222 // 5) ensure the derived address is correct
1223 getFirstAddress(function(address
) {
1224 expect(address
).toBe("Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT");
1231 // Selecting a language with no existing phrase should generate a phrase in
1233 it('Generate a random phrase when language is selected and no current phrase', function(done
) {
1234 driver
.findElement(By
.css("a[href='#japanese']"))
1236 driver
.sleep(generateDelay
).then(function() {
1237 driver
.findElement(By
.css(".phrase"))
1238 .getAttribute("value").then(function(phrase
) {
1239 expect(phrase
.search(/[a-z]/)).toBe(-1);
1240 expect(phrase
.length
).toBeGreaterThan(0);
1246 // Selecting a language with existing phrase should update the phrase to use
1248 it('Updates existing phrases when the language is changed', function(done
) {
1249 driver
.findElement(By
.css(".phrase"))
1250 .sendKeys("abandon abandon ability");
1251 driver
.sleep(generateDelay
).then(function() {
1252 driver
.findElement(By
.css("a[href='#italian']"))
1254 driver
.sleep(generateDelay
).then(function() {
1255 driver
.findElement(By
.css(".phrase"))
1256 .getAttribute("value").then(function(phrase
) {
1257 // Check only the language changes, not the phrase
1258 expect(phrase
).toBe("abaco abaco abbaglio");
1259 getFirstAddress(function(address
) {
1260 // Check the address is correct
1261 expect(address
).toBe("1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV");
1269 // Suggested replacement for erroneous word in non-English language
1270 it('Shows word suggestion for incorrect word in non-English language', function(done
) {
1271 driver
.findElement(By
.css('.phrase'))
1272 .sendKeys('abaco abaco zbbaglio');
1273 driver
.sleep(feedbackDelay
).then(function() {
1274 driver
.findElement(By
.css('.feedback'))
1276 .then(function(feedback
) {
1277 var msg
= "zbbaglio not in wordlist, did you mean abbaglio?";
1278 expect(feedback
).toBe(msg
);
1284 // Japanese word does not break across lines.
1286 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1287 it('Does not break Japanese words across lines', function(done
) {
1288 driver
.findElement(By
.css('.phrase'))
1289 .getCssValue("word-break")
1290 .then(function(value
) {
1291 expect(value
).toBe("keep-all");
1296 // Language can be specified at page load using hash value in url
1297 it('Can set the language from the url hash', function(done
) {
1298 driver
.get(url
+ "#japanese").then(function() {
1299 driver
.findElement(By
.css('.generate')).click();
1300 driver
.sleep(generateDelay
).then(function() {
1301 driver
.findElement(By
.css(".phrase"))
1302 .getAttribute("value").then(function(phrase
) {
1303 expect(phrase
.search(/[a-z]/)).toBe(-1);
1304 expect(phrase
.length
).toBeGreaterThan(0);
1311 // Entropy can be entered by the user
1312 it('Allows entropy to be entered', function(done
) {
1313 driver
.findElement(By
.css('.use-entropy'))
1315 driver
.findElement(By
.css('.entropy'))
1316 .sendKeys('00000000 00000000 00000000 00000000');
1317 driver
.sleep(generateDelay
).then(function() {
1318 driver
.findElement(By
.css(".phrase"))
1319 .getAttribute("value").then(function(phrase
) {
1320 expect(phrase
).toBe("abandon abandon ability");
1321 getFirstAddress(function(address
) {
1322 expect(address
).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug");
1329 // A warning about entropy is shown to the user, with additional information
1330 it('Shows a warning about using entropy', function(done
) {
1331 driver
.findElement(By
.css('.use-entropy'))
1333 driver
.findElement(By
.css('.entropy-container'))
1335 .then(function(containerText
) {
1336 var warning
= "mnemonic may be insecure";
1337 expect(containerText
).toContain(warning
);
1338 driver
.findElement(By
.css('#entropy-notes'))
1339 .findElement(By
.xpath("parent::*"))
1341 .then(function(notesText
) {
1342 var detail
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
1343 expect(notesText
).toContain(detail
);
1349 // The types of entropy available are described to the user
1350 it('Shows the types of entropy available', function(done
) {
1351 driver
.findElement(By
.css('.entropy'))
1352 .getAttribute("placeholder")
1353 .then(function(placeholderText
) {
1362 for (var i
=0; i
<options
.length
; i
++) {
1363 var option
= options
[i
];
1364 expect(placeholderText
).toContain(option
);
1370 // The actual entropy used is shown to the user
1371 it('Shows the actual entropy used', function(done
) {
1372 driver
.findElement(By
.css('.use-entropy'))
1374 driver
.findElement(By
.css('.entropy'))
1375 .sendKeys('Not A Very Good Entropy Source At All');
1376 driver
.sleep(generateDelay
).then(function() {
1377 driver
.findElement(By
.css('.entropy-container'))
1379 .then(function(text
) {
1380 expect(text
).toMatch(/Filtered Entropy
\s
+AedEceAA
/);
1386 // Binary entropy can be entered
1387 it('Allows binary entropy to be entered', function(done
) {
1388 testEntropyType(done
, "01", "binary");
1391 // Base 6 entropy can be entered
1392 it('Allows base 6 entropy to be entered', function(done
) {
1393 testEntropyType(done
, "012345", "base 6");
1396 // Base 6 dice entropy can be entered
1397 it('Allows base 6 dice entropy to be entered', function(done
) {
1398 testEntropyType(done
, "123456", "base 6 (dice)");
1401 // Base 10 entropy can be entered
1402 it('Allows base 10 entropy to be entered', function(done
) {
1403 testEntropyType(done
, "789", "base 10");
1406 // Hexadecimal entropy can be entered
1407 it('Allows hexadecimal entropy to be entered', function(done
) {
1408 testEntropyType(done
, "abcdef", "hexadecimal");
1411 // Dice entropy value is shown as the converted base 6 value
1412 // ie 123456 is converted to 123450
1413 it('Shows dice entropy as base 6', function(done
) {
1414 driver
.findElement(By
.css('.use-entropy'))
1416 driver
.findElement(By
.css('.entropy'))
1417 .sendKeys("123456");
1418 driver
.sleep(generateDelay
).then(function() {
1419 driver
.findElement(By
.css('.entropy-container'))
1421 .then(function(text
) {
1422 expect(text
).toMatch(/Filtered Entropy
\s
+123450/);
1428 // The number of bits of entropy accumulated is shown
1429 it("Shows the number of bits of entropy for 20 bits of binary", function(done
) {
1430 testEntropyBits(done
, "0000 0000 0000 0000 0000", "20");
1432 it("Shows the number of bits of entropy for 1 bit of binary", function(done
) {
1433 testEntropyBits(done
, "0", "1");
1435 it("Shows the number of bits of entropy for 4 bits of binary", function(done
) {
1436 testEntropyBits(done
, "0000", "4");
1438 it("Shows the number of bits of entropy for 1 character of base 6 (dice)", function(done
) {
1439 // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
1440 testEntropyBits(done
, "6", "2");
1442 it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits", function(done
) {
1443 // 7 in base 10 is 111 in base 2, no leading zeros
1444 testEntropyBits(done
, "7", "3");
1446 it("Shows the number of bits of entropy for 1 character of base 10 with 4 bis", function(done
) {
1447 testEntropyBits(done
, "8", "4");
1449 it("Shows the number of bits of entropy for 1 character of hex", function(done
) {
1450 testEntropyBits(done
, "F", "4");
1452 it("Shows the number of bits of entropy for 2 characters of base 10", function(done
) {
1453 testEntropyBits(done
, "29", "6");
1455 it("Shows the number of bits of entropy for 2 characters of hex", function(done
) {
1456 testEntropyBits(done
, "0A", "8");
1458 it("Shows the number of bits of entropy for 2 characters of hex with 3 leading zeros", function(done
) {
1459 // hex is always multiple of 4 bits of entropy
1460 testEntropyBits(done
, "1A", "8");
1462 it("Shows the number of bits of entropy for 2 characters of hex with 2 leading zeros", function(done
) {
1463 testEntropyBits(done
, "2A", "8");
1465 it("Shows the number of bits of entropy for 2 characters of hex with 1 leading zero", function(done
) {
1466 testEntropyBits(done
, "4A", "8");
1468 it("Shows the number of bits of entropy for 2 characters of hex with no leading zeros", function(done
) {
1469 testEntropyBits(done
, "8A", "8");
1471 it("Shows the number of bits of entropy for 2 characters of hex starting with F", function(done
) {
1472 testEntropyBits(done
, "FA", "8");
1474 it("Shows the number of bits of entropy for 4 characters of hex with leading zeros", function(done
) {
1475 testEntropyBits(done
, "000A", "16");
1477 it("Shows the number of bits of entropy for 4 characters of base 6", function(done
) {
1478 testEntropyBits(done
, "5555", "11");
1480 it("Shows the number of bits of entropy for 4 characters of base 6 dice", function(done
) {
1481 // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of
1482 // 2.58 bits, which is 10.32 bits (rounded down to 10 bits)
1483 testEntropyBits(done
, "6666", "10");
1485 it("Shows the number of bits of entropy for 4 charactes of base 10", function(done
) {
1486 // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded
1488 testEntropyBits(done
, "2227", "13");
1490 it("Shows the number of bits of entropy for 4 characters of hex with 2 leading zeros", function(done
) {
1491 testEntropyBits(done
, "222F", "16");
1493 it("Shows the number of bits of entropy for 4 characters of hex starting with F", function(done
) {
1494 testEntropyBits(done
, "FFFF", "16");
1496 it("Shows the number of bits of entropy for 10 characters of base 10", function(done
) {
1497 // 10 events at 3.32 bits per event
1498 testEntropyBits(done
, "0000101017", "33");
1500 it("Shows the number of bits of entropy for a full deck of cards", function(done
) {
1501 // cards are not replaced, so a full deck is not 52^52 entropy which is 296
1502 // bits, it's 52!, which is 225 bits
1503 testEntropyBits(done
, "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225");
1506 it("Shows details about the entered entropy", function(done
) {
1507 testEntropyFeedback(done
,
1511 type: "hexadecimal",
1515 strength: "less than a second",
1519 it("Shows details about the entered entropy", function(done
) {
1520 testEntropyFeedback(done
,
1522 entropy: "AAAAAAAA",
1523 filtered: "AAAAAAAA",
1524 type: "hexadecimal",
1528 strength: "less than a second - Repeats like \"aaa\" are easy to guess",
1532 it("Shows details about the entered entropy", function(done
) {
1533 testEntropyFeedback(done
,
1535 entropy: "AAAAAAAA B",
1536 filtered: "AAAAAAAAB",
1537 type: "hexadecimal",
1541 strength: "less than a second - Repeats like \"aaa\" are easy to guess",
1545 it("Shows details about the entered entropy", function(done
) {
1546 testEntropyFeedback(done
,
1548 entropy: "AAAAAAAA BBBBBBBB",
1549 filtered: "AAAAAAAABBBBBBBB",
1550 type: "hexadecimal",
1554 strength: "less than a second - Repeats like \"aaa\" are easy to guess",
1558 it("Shows details about the entered entropy", function(done
) {
1559 testEntropyFeedback(done
,
1561 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
1562 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
1563 type: "hexadecimal",
1567 strength: "less than a second",
1571 it("Shows details about the entered entropy", function(done
) {
1572 testEntropyFeedback(done
,
1574 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
1575 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
1576 type: "hexadecimal",
1580 strength: "2 minutes",
1584 it("Shows details about the entered entropy", function(done
) {
1585 testEntropyFeedback(done
,
1587 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
1588 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
1589 type: "hexadecimal",
1597 it("Shows details about the entered entropy", function(done
) {
1598 testEntropyFeedback(done
,
1600 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
1601 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
1602 type: "hexadecimal",
1606 strength: "3 years",
1610 it("Shows details about the entered entropy", function(done
) {
1611 testEntropyFeedback(done
,
1613 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
1614 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
1615 type: "hexadecimal",
1619 strength: "centuries",
1623 it("Shows details about the entered entropy", function(done
) {
1624 testEntropyFeedback(done
,
1631 strength: "less than a second",
1635 it("Shows details about the entered entropy", function(done
) {
1636 testEntropyFeedback(done
,
1638 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1639 type: "card (full deck)",
1643 strength: "centuries",
1647 it("Shows details about the entered entropy", function(done
) {
1648 testEntropyFeedback(done
,
1650 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
1651 type: "card (full deck, 1 duplicate: 3d)",
1655 strength: "centuries",
1659 it("Shows details about the entered entropy", function(done
) {
1660 testEntropyFeedback(done
,
1662 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
1663 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
1667 strength: "centuries",
1671 it("Shows details about the entered entropy", function(done
) {
1672 testEntropyFeedback(done
,
1674 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
1675 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
1679 strength: "centuries",
1683 it("Shows details about the entered entropy", function(done
) {
1684 testEntropyFeedback(done
,
1685 // Next test was throwing uncaught error in zxcvbn
1686 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
1688 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1689 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
1693 strength: "centuries",
1697 it("Shows details about the entered entropy", function(done
) {
1698 testEntropyFeedback(done
,
1699 // Case insensitivity to duplicate cards
1702 type: "card (1 duplicate: AS)",
1706 strength: "less than a second",
1710 it("Shows details about the entered entropy", function(done
) {
1711 testEntropyFeedback(done
,
1714 type: "card (1 duplicate: as)",
1718 strength: "less than a second",
1722 it("Shows details about the entered entropy", function(done
) {
1723 testEntropyFeedback(done
,
1724 // Missing cards are detected
1726 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1727 type: "card (1 missing: 9C)",
1731 strength: "centuries",
1735 it("Shows details about the entered entropy", function(done
) {
1736 testEntropyFeedback(done
,
1738 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1739 type: "card (2 missing: 9C 5D)",
1743 strength: "centuries",
1747 it("Shows details about the entered entropy", function(done
) {
1748 testEntropyFeedback(done
,
1750 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
1751 type: "card (4 missing: 9C 5D QD...)",
1755 strength: "centuries",
1759 it("Shows details about the entered entropy", function(done
) {
1760 testEntropyFeedback(done
,
1761 // More than six missing cards does not show message
1763 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
1768 strength: "centuries",
1772 it("Shows details about the entered entropy", function(done
) {
1773 testEntropyFeedback(done
,
1774 // Multiple decks of cards increases bits per event
1779 bitsPerEvent: "4.34",
1783 it("Shows details about the entered entropy", function(done
) {
1784 testEntropyFeedback(done
,
1789 bitsPerEvent: "4.80",
1793 it("Shows details about the entered entropy", function(done
) {
1794 testEntropyFeedback(done
,
1799 bitsPerEvent: "5.01",
1803 it("Shows details about the entered entropy", function(done
) {
1804 testEntropyFeedback(done
,
1806 entropy: "3d3d3d3d",
1809 bitsPerEvent: "5.14",
1813 it("Shows details about the entered entropy", function(done
) {
1814 testEntropyFeedback(done
,
1816 entropy: "3d3d3d3d3d",
1819 bitsPerEvent: "5.22",
1823 it("Shows details about the entered entropy", function(done
) {
1824 testEntropyFeedback(done
,
1826 entropy: "3d3d3d3d3d3d",
1829 bitsPerEvent: "5.28",
1833 it("Shows details about the entered entropy", function(done
) {
1834 testEntropyFeedback(done
,
1836 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
1839 bitsPerEvent: "5.59",
1840 strength: 'less than a second - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
1845 // Entropy is truncated from the left
1846 it('Truncates entropy from the left', function(done
) {
1847 // Truncate from left means 0000 is removed from the start
1848 // which gives mnemonic 'avocado zoo zone'
1849 // not 1111 removed from the end
1850 // which gives the mnemonic 'abstract zoo zoo'
1851 var entropy
= "00000000 00000000 00000000 00000000";
1852 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte
1853 driver
.findElement(By
.css('.use-entropy'))
1855 driver
.findElement(By
.css('.entropy'))
1857 driver
.sleep(generateDelay
).then(function() {
1858 driver
.findElement(By
.css(".phrase"))
1859 .getAttribute("value").then(function(phrase
) {
1860 expect(phrase
).toBe("avocado zoo zone");
1866 // Very large entropy results in very long mnemonics
1867 it('Converts very long entropy to very long mnemonics', function(done
) {
1869 for (var i
=0; i
<33; i
++) {
1870 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
1872 driver
.findElement(By
.css('.use-entropy'))
1874 driver
.findElement(By
.css('.entropy'))
1876 driver
.sleep(generateDelay
).then(function() {
1877 driver
.findElement(By
.css(".phrase"))
1878 .getAttribute("value").then(function(phrase
) {
1879 var wordCount
= phrase
.split(/\s+/g).length
;
1880 expect(wordCount
).toBe(99);
1886 // Is compatible with bip32jp entropy
1887 // https://bip32jp.github.io/english/index.html
1889 // Is incompatible with:
1891 it('Is compatible with bip32jp.github.io', function(done
) {
1892 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
1893 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";
1894 driver
.findElement(By
.css('.use-entropy'))
1896 driver
.findElement(By
.css('.entropy'))
1898 driver
.sleep(generateDelay
).then(function() {
1899 driver
.findElement(By
.css(".phrase"))
1900 .getAttribute("value").then(function(phrase
) {
1901 expect(phrase
).toBe(expectedPhrase
);
1907 // Blank entropy does not generate mnemonic or addresses
1908 it('Does not generate mnemonic for blank entropy', function(done
) {
1909 driver
.findElement(By
.css('.use-entropy'))
1911 driver
.findElement(By
.css('.entropy'))
1913 // check there is no mnemonic
1914 driver
.sleep(generateDelay
).then(function() {
1915 driver
.findElement(By
.css(".phrase"))
1916 .getAttribute("value").then(function(phrase
) {
1917 expect(phrase
).toBe("");
1918 // check there is no mnemonic
1919 driver
.findElements(By
.css(".address"))
1920 .then(function(addresses
) {
1921 expect(addresses
.length
).toBe(0);
1922 // Check the feedback says 'blank entropy'
1923 driver
.findElement(By
.css(".feedback"))
1925 .then(function(feedbackText
) {
1926 expect(feedbackText
).toBe("Blank entropy");
1934 // Mnemonic length can be selected even for weak entropy
1935 it('Allows selection of mnemonic length even for weak entropy', function(done
) {
1936 driver
.findElement(By
.css('.use-entropy'))
1938 driver
.executeScript(function() {
1939 $(".mnemonic-length").val("18").trigger("change");
1941 driver
.findElement(By
.css('.entropy'))
1942 .sendKeys("012345");
1943 driver
.sleep(generateDelay
).then(function() {
1944 driver
.findElement(By
.css(".phrase"))
1945 .getAttribute("value").then(function(phrase
) {
1946 var wordCount
= phrase
.split(/\s+/g).length
;
1947 expect(wordCount
).toBe(18);
1954 // https://github.com/iancoleman/bip39/issues/33
1955 // Final cards should contribute entropy
1956 it('Uses as much entropy as possible for the mnemonic', function(done
) {
1957 driver
.findElement(By
.css('.use-entropy'))
1959 driver
.findElement(By
.css('.entropy'))
1960 .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");
1961 driver
.sleep(generateDelay
).then(function() {
1963 driver
.findElement(By
.css(".phrase"))
1964 .getAttribute("value").then(function(originalPhrase
) {
1965 // Set the last 12 cards to be AS
1966 driver
.findElement(By
.css('.entropy'))
1968 driver
.findElement(By
.css('.entropy'))
1969 .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");
1970 driver
.sleep(generateDelay
).then(function() {
1972 driver
.findElement(By
.css(".phrase"))
1973 .getAttribute("value").then(function(newPhrase
) {
1974 expect(originalPhrase
).not
.toEqual(newPhrase
);
1983 // https://github.com/iancoleman/bip39/issues/35
1985 // TODO this doesn't work in selenium with firefox
1986 // see https://stackoverflow.com/q/40360223
1987 it('Shows a qr code on hover for the phrase', function(done
) {
1988 if (browser
== "firefox") {
1989 pending("Selenium + Firefox bug for mouseMove, see https://stackoverflow.com/q/40360223");
1991 // generate a random mnemonic
1992 var generateEl
= driver
.findElement(By
.css('.generate'));
1994 // toggle qr to show (hidden by default)
1995 var phraseEl
= driver
.findElement(By
.css(".phrase"));
1997 var rootKeyEl
= driver
.findElement(By
.css(".root-key"));
1998 driver
.sleep(generateDelay
).then(function() {
1999 // hover over the root key
2000 driver
.actions().mouseMove(rootKeyEl
).perform().then(function() {
2001 // check the qr code shows
2002 driver
.executeScript(function() {
2003 return $(".qr-container").find("canvas").length
> 0;
2005 .then(function(qrShowing
) {
2006 expect(qrShowing
).toBe(true);
2007 // hover away from the phrase
2008 driver
.actions().mouseMove(generateEl
).perform().then(function() {;
2009 // check the qr code hides
2010 driver
.executeScript(function() {
2011 return $(".qr-container").find("canvas").length
== 0;
2013 .then(function(qrHidden
) {
2014 expect(qrHidden
).toBe(true);
2023 // BIP44 account extendend private key is shown
2024 // github issue 37 - compatibility with electrum
2025 it('Shows the bip44 account extended private key', function(done
) {
2026 driver
.findElement(By
.css(".phrase"))
2027 .sendKeys("abandon abandon ability");
2028 driver
.sleep(generateDelay
).then(function() {
2029 driver
.findElement(By
.css("#bip44 .account-xprv"))
2030 .getAttribute("value")
2031 .then(function(xprv
) {
2032 expect(xprv
).toBe("xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ");
2038 // BIP44 account extendend public key is shown
2039 // github issue 37 - compatibility with electrum
2040 it('Shows the bip44 account extended public key', function(done
) {
2041 driver
.findElement(By
.css(".phrase"))
2042 .sendKeys("abandon abandon ability");
2043 driver
.sleep(generateDelay
).then(function() {
2044 driver
.findElement(By
.css("#bip44 .account-xpub"))
2045 .getAttribute("value")
2046 .then(function(xprv
) {
2047 expect(xprv
).toBe("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2054 // BIP32 root key can be set as an xpub
2055 it('Generates addresses from xpub as bip32 root key', function(done
) {
2056 driver
.findElement(By
.css('#bip32-tab a'))
2058 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2059 driver
.findElement(By
.css("#root-key"))
2060 .sendKeys("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2061 driver
.sleep(generateDelay
).then(function() {
2062 // check the addresses are generated
2063 getFirstAddress(function(address
) {
2064 expect(address
).toBe("1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug");
2065 // check the xprv key is not set
2066 driver
.findElement(By
.css(".extended-priv-key"))
2067 .getAttribute("value")
2068 .then(function(xprv
) {
2069 expect(xprv
).toBe("NA");
2070 // check the private key is not set
2071 driver
.findElements(By
.css(".privkey"))
2072 .then(function(els
) {
2075 .then(function(privkey
) {
2076 expect(xprv
).toBe("NA");
2086 // xpub for bip32 root key will not work with hardened derivation paths
2087 it('Shows error for hardened derivation paths with xpub root key', function(done
) {
2088 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2089 driver
.findElement(By
.css("#root-key"))
2090 .sendKeys("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2091 driver
.sleep(feedbackDelay
).then(function() {
2092 // Check feedback is correct
2093 driver
.findElement(By
.css('.feedback'))
2095 .then(function(feedback
) {
2096 var msg
= "Hardened derivation path is invalid with xpub key";
2097 expect(feedback
).toBe(msg
);
2098 // Check no addresses are shown
2099 driver
.findElements(By
.css('.addresses tr'))
2100 .then(function(rows
) {
2101 expect(rows
.length
).toBe(0);
2109 // no root key shows feedback
2110 it('Shows feedback for no root key', function(done
) {
2111 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2112 driver
.findElement(By
.css('#bip32-tab a'))
2114 driver
.sleep(feedbackDelay
).then(function() {
2115 // Check feedback is correct
2116 driver
.findElement(By
.css('.feedback'))
2118 .then(function(feedback
) {
2119 expect(feedback
).toBe("Invalid root key");
2126 // display error switching tabs while addresses are generating
2127 it('Can change details while old addresses are still being generated', function(done
) {
2128 // Set to generate 199 more addresses.
2129 // This will take a long time allowing a new set of addresses to be
2130 // generated midway through this lot.
2131 // The newly generated addresses should not include any from the old set.
2132 // Any more than 199 will show an alert which needs to be accepted.
2133 driver
.findElement(By
.css('.rows-to-add'))
2135 driver
.findElement(By
.css('.rows-to-add'))
2138 driver
.findElement(By
.css('.phrase'))
2139 .sendKeys("abandon abandon ability");
2140 driver
.sleep(generateDelay
).then(function() {
2141 // generate more addresses
2142 driver
.findElement(By
.css('.more'))
2144 // change tabs which should cancel the previous generating
2145 driver
.findElement(By
.css('#bip32-tab a'))
2147 driver
.sleep(generateDelay
).then(function() {
2148 driver
.findElements(By
.css('.index'))
2149 .then(function(els
) {
2150 // check the derivation paths have the right quantity
2151 expect(els
.length
).toBe(20);
2152 // check the derivation paths are in order
2153 testRowsAreInCorrectOrder(done
);
2160 // padding for binary should give length with multiple of 256
2161 // hashed entropy 1111 is length 252, so requires 4 leading zeros
2162 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
2163 it('Pads hashed entropy with leading zeros', function(done
) {
2164 driver
.findElement(By
.css('.use-entropy'))
2166 driver
.executeScript(function() {
2167 $(".mnemonic-length").val("15").trigger("change");
2169 driver
.findElement(By
.css('.entropy'))
2171 driver
.sleep(generateDelay
).then(function() {
2172 driver
.findElement(By
.css('.phrase'))
2173 .getAttribute("value")
2174 .then(function(phrase
) {
2175 expect(phrase
).toBe("avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear");
2181 // Github pull request 55
2182 // https://github.com/iancoleman/bip39/pull/55
2184 it('Can set the derivation path on bip32 tab for bitcoincore', function(done
) {
2185 testClientSelect(done
, {
2187 bip32path: "m/0'/0'",
2188 useHardenedAddresses: "true",
2191 it('Can set the derivation path on bip32 tab for multibit', function(done
) {
2192 testClientSelect(done
, {
2194 bip32path: "m/0'/0",
2195 useHardenedAddresses: null,
2200 // https://github.com/iancoleman/bip39/issues/58
2201 // bip32 derivation is correct, does not drop leading zeros
2203 // https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846
2204 it('Retains leading zeros for bip32 derivation', function(done
) {
2205 driver
.findElement(By
.css(".phrase"))
2206 .sendKeys("fruit wave dwarf banana earth journey tattoo true farm silk olive fence");
2207 driver
.findElement(By
.css(".passphrase"))
2208 .sendKeys("banana");
2209 driver
.sleep(generateDelay
).then(function() {
2210 getFirstAddress(function(address
) {
2211 // Note that bitcore generates an incorrect address
2212 // 13EuKhffWkBE2KUwcbkbELZb1MpzbimJ3Y
2213 // see the medium.com link above for more details
2214 expect(address
).toBe("17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F");
2221 // Japanese mnemonics generate incorrect bip32 seed
2222 // BIP39 seed is set from phrase
2223 it('Generates correct seed for Japanese mnemonics', function(done
) {
2224 driver
.findElement(By
.css(".phrase"))
2225 .sendKeys("あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら");
2226 driver
.findElement(By
.css(".passphrase"))
2227 .sendKeys("メートルガバヴァぱばぐゞちぢ十人十色");
2228 driver
.sleep(generateDelay
).then(function() {
2229 driver
.findElement(By
.css(".seed"))
2230 .getAttribute("value")
2231 .then(function(seed
) {
2232 expect(seed
).toBe("a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55");
2238 // BIP49 official test vectors
2239 // https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki#test-vectors
2240 it('Generates BIP49 addresses matching the official test vectors', function(done
) {
2241 driver
.findElement(By
.css('#bip49-tab a'))
2243 selectNetwork("BTC - Bitcoin Testnet");
2244 driver
.findElement(By
.css(".phrase"))
2245 .sendKeys("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
2246 driver
.sleep(generateDelay
).then(function() {
2247 getFirstAddress(function(address
) {
2248 expect(address
).toBe("2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2");
2254 // BIP49 derivation path is shown
2255 it('Shows the bip49 derivation path', function(done
) {
2256 driver
.findElement(By
.css('#bip49-tab a'))
2258 driver
.findElement(By
.css(".phrase"))
2259 .sendKeys("abandon abandon ability");
2260 driver
.sleep(generateDelay
).then(function() {
2261 driver
.findElement(By
.css('#bip49 .path'))
2262 .getAttribute("value")
2263 .then(function(path
) {
2264 expect(path
).toBe("m/49'/0'/0'/0");
2270 // BIP49 extended private key is shown
2271 it('Shows the bip49 extended private key', function(done
) {
2272 driver
.findElement(By
.css('#bip49-tab a'))
2274 driver
.findElement(By
.css(".phrase"))
2275 .sendKeys("abandon abandon ability");
2276 driver
.sleep(generateDelay
).then(function() {
2277 driver
.findElement(By
.css('.extended-priv-key'))
2278 .getAttribute("value")
2279 .then(function(xprv
) {
2280 expect(xprv
).toBe("yprvALYB4DYRG6CzzVgzQZwwqjAA2wjBGC3iEd7KYYScpoDdmf75qMRWZWxoFcRXBJjgEXdFqJ9vDRGRLJQsrL22Su5jMbNFeM9vetaGVqy9Qy2");
2286 // BIP49 extended public key is shown
2287 it('Shows the bip49 extended public key', function(done
) {
2288 driver
.findElement(By
.css('#bip49-tab a'))
2290 driver
.findElement(By
.css(".phrase"))
2291 .sendKeys("abandon abandon ability");
2292 driver
.sleep(generateDelay
).then(function() {
2293 driver
.findElement(By
.css('.extended-pub-key'))
2294 .getAttribute("value")
2295 .then(function(xprv
) {
2296 expect(xprv
).toBe("ypub6ZXXTj5K6TmJCymTWbUxCs6tayZffemZbr2vLvrEP8kceTSENtjm7KHH6thvAKxVar9fGe8rgsPEX369zURLZ68b4f7Vexz7RuXsjQ69YDt");
2302 // BIP49 account field changes address list
2303 it('Can set the bip49 account field', function(done
) {
2304 driver
.findElement(By
.css('#bip49-tab a'))
2306 driver
.findElement(By
.css('#bip49 .account'))
2308 driver
.findElement(By
.css('#bip49 .account'))
2310 driver
.findElement(By
.css(".phrase"))
2311 .sendKeys("abandon abandon ability");
2312 driver
.sleep(generateDelay
).then(function() {
2313 getFirstAddress(function(address
) {
2314 expect(address
).toBe("381wg1GGN4rP88rNC9v7QWsiww63yLVPsn");
2320 // BIP49 change field changes address list
2321 it('Can set the bip49 change field', function(done
) {
2322 driver
.findElement(By
.css('#bip49-tab a'))
2324 driver
.findElement(By
.css('#bip49 .change'))
2326 driver
.findElement(By
.css('#bip49 .change'))
2328 driver
.findElement(By
.css(".phrase"))
2329 .sendKeys("abandon abandon ability");
2330 driver
.sleep(generateDelay
).then(function() {
2331 getFirstAddress(function(address
) {
2332 expect(address
).toBe("3PEM7MiKed5konBoN66PQhK8r3hjGhy9dT");
2338 // BIP49 account extendend private key is shown
2339 it('Shows the bip49 account extended private key', function(done
) {
2340 driver
.findElement(By
.css('#bip49-tab a'))
2342 driver
.findElement(By
.css(".phrase"))
2343 .sendKeys("abandon abandon ability");
2344 driver
.sleep(generateDelay
).then(function() {
2345 driver
.findElement(By
.css('#bip49 .account-xprv'))
2346 .getAttribute("value")
2347 .then(function(xprv
) {
2348 expect(xprv
).toBe("yprvAHtB1M5Wp675aLzFy9TJYK2mSsLkg6mcBRh5DZTR7L4EnYSmYPaL63KFA4ycg1PngW5LfkmejxzosCs17TKZMpRFKc3z5SJar6QAKaFcaZL");
2354 // BIP49 account extendend public key is shown
2355 it('Shows the bip49 account extended public key', function(done
) {
2356 driver
.findElement(By
.css('#bip49-tab a'))
2358 driver
.findElement(By
.css(".phrase"))
2359 .sendKeys("abandon abandon ability");
2360 driver
.sleep(generateDelay
).then(function() {
2361 driver
.findElement(By
.css('#bip49 .account-xpub'))
2362 .getAttribute("value")
2363 .then(function(xprv
) {
2364 expect(xprv
).toBe("ypub6WsXQrcQeTfNnq4j5AzJuSyVzuBF5ZVTYecg1ws2ffbDfLmv5vtadqdj1NgR6C6gufMpMfJpHxvb6JEQKvETVNWCRanNedfJtnTchZiJtsL");
2370 // Test selecting coin where bip49 is unavailable (eg CLAM)
2371 it('Shows an error on bip49 tab for coins without bip49', function(done
) {
2372 driver
.findElement(By
.css('#bip49-tab a'))
2374 driver
.findElement(By
.css(".phrase"))
2375 .sendKeys("abandon abandon ability");
2376 driver
.sleep(generateDelay
).then(function() {
2377 selectNetwork("CLAM - Clams");
2378 // bip49 available is hidden
2379 driver
.findElement(By
.css('#bip49 .available'))
2380 .getAttribute("class")
2381 .then(function(classes
) {
2382 expect(classes
).toContain("hidden");
2383 // bip49 unavailable is shown
2384 driver
.findElement(By
.css('#bip49 .unavailable'))
2385 .getAttribute("class")
2386 .then(function(classes
) {
2387 expect(classes
).not
.toContain("hidden");
2388 // check there are no addresses shown
2389 driver
.findElements(By
.css('.addresses tr'))
2390 .then(function(rows
) {
2391 expect(rows
.length
).toBe(0);
2392 // check the derived private key is blank
2393 driver
.findElement(By
.css('.extended-priv-key'))
2394 .getAttribute("value")
2395 .then(function(xprv
) {
2396 expect(xprv
).toBe('');
2397 // check the derived public key is blank
2398 driver
.findElement(By
.css('.extended-pub-key'))
2399 .getAttribute("value")
2400 .then(function(xpub
) {
2401 expect(xpub
).toBe('');
2412 // Cleared mnemonic and root key still allows addresses to be generated
2413 // https://github.com/iancoleman/bip39/issues/43
2414 it('Clears old root keys from memory when mnemonic is cleared', function(done
) {
2416 driver
.findElement(By
.css(".phrase"))
2417 .sendKeys("abandon abandon ability");
2418 driver
.sleep(generateDelay
).then(function() {
2419 // clear the mnemonic and root key
2420 // using selenium .clear() doesn't seem to trigger the 'input' event
2421 // so clear it using keys instead
2422 driver
.findElement(By
.css('.phrase'))
2423 .sendKeys(Key
.CONTROL
,"a");
2424 driver
.findElement(By
.css('.phrase'))
2425 .sendKeys(Key
.DELETE
);
2426 driver
.findElement(By
.css('.root-key'))
2427 .sendKeys(Key
.CONTROL
,"a");
2428 driver
.findElement(By
.css('.root-key'))
2429 .sendKeys(Key
.DELETE
);
2430 driver
.sleep(generateDelay
).then(function() {
2431 // try to generate more addresses
2432 driver
.findElement(By
.css('.more'))
2434 driver
.sleep(generateDelay
).then(function() {
2435 driver
.findElements(By
.css(".addresses tr"))
2436 .then(function(els
) {
2437 // check there are no addresses shown
2438 expect(els
.length
).toBe(0);
2447 // error trying to generate addresses from xpub with hardened derivation
2448 it('Shows error for hardened addresses with xpub root key', function(done
) {
2449 driver
.findElement(By
.css('#bip32-tab a'))
2451 driver
.executeScript(function() {
2452 $(".hardened-addresses").prop("checked", true);
2454 // set xpub for account 0 of bip44 for 'abandon abandon ability'
2455 driver
.findElement(By
.css("#root-key"))
2456 .sendKeys("xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf");
2457 driver
.sleep(feedbackDelay
).then(function() {
2458 // Check feedback is correct
2459 driver
.findElement(By
.css('.feedback'))
2461 .then(function(feedback
) {
2462 var msg
= "Hardened derivation path is invalid with xpub key";
2463 expect(feedback
).toBe(msg
);
2469 // Litecoin uses xprv by default, and can optionally be set to ltpv
2471 // https://github.com/iancoleman/bip39/issues/96
2472 // Issue with extended keys on Litecoin
2473 it('Uses xprv by default for litecoin, but can be set to ltpv', function(done
) {
2474 driver
.findElement(By
.css('.phrase'))
2475 .sendKeys("abandon abandon ability");
2476 selectNetwork("LTC - Litecoin");
2477 driver
.sleep(generateDelay
).then(function() {
2478 // check the extended key is generated correctly
2479 driver
.findElement(By
.css('.root-key'))
2480 .getAttribute("value")
2481 .then(function(rootKey
) {
2482 expect(rootKey
).toBe("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi");
2483 // set litecoin to use ltub
2484 driver
.executeScript(function() {
2485 $(".litecoin-use-ltub").prop("checked", true);
2486 $(".litecoin-use-ltub").trigger("change");
2488 driver
.sleep(generateDelay
).then(function() {
2489 driver
.findElement(By
.css('.root-key'))
2490 .getAttribute("value")
2491 .then(function(rootKey
) {
2492 expect(rootKey
).toBe("Ltpv71G8qDifUiNesiPqf6h5V6eQ8ic77oxQiYtawiACjBEx3sTXNR2HGDGnHETYxESjqkMLFBkKhWVq67ey1B2MKQXannUqNy1RZVHbmrEjnEU");
2500 // BIP32 tab can use P2WPKH Nested In P2SH
2501 // github issue 91 part 2
2502 // https://github.com/iancoleman/bip39/issues/91
2503 // generate new addresses from xpub?
2504 it('Uses xprv by default for litecoin, but can be set to ltpv', function(done
) {
2505 // use p2wpkh addresses
2506 driver
.executeScript(function() {
2507 $(".p2wpkh-nested-in-p2sh").prop("checked", true);
2510 driver
.findElement(By
.css('#bip32-tab a'))
2513 selectNetwork("BTC - Bitcoin Testnet");
2514 // Set root xpub to BIP49 official test vector account 0
2515 driver
.findElement(By
.css('.root-key'))
2516 .sendKeys("tpubDD7tXK8KeQ3YY83yWq755fHY2JW8Ha8Q765tknUM5rSvjPcGWfUppDFMpQ1ScziKfW3ZNtZvAD7M3u7bSs7HofjTD3KP3YxPK7X6hwV8Rk2");
2517 driver
.sleep(generateDelay
).then(function() {
2518 getFirstAddress(function(address
) {
2519 expect(address
).toBe("2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2");
2526 // https://github.com/iancoleman/bip39/issues/99#issuecomment-327094159
2527 // "warn me emphatically when they have detected invalid input" to the entropy field
2528 // A warning is shown when entropy is filtered and discarded
2529 it('Warns when entropy is filtered and discarded', function(done
) {
2530 driver
.findElement(By
.css('.use-entropy'))
2532 // set entropy to have no filtered content
2533 driver
.findElement(By
.css('.entropy'))
2534 .sendKeys("00000000 00000000 00000000 00000000");
2535 driver
.sleep(generateDelay
).then(function() {
2536 // check the filter warning does not show
2537 driver
.findElement(By
.css('.entropy-container .filter-warning'))
2538 .getAttribute("class")
2539 .then(function(classes
) {
2540 expect(classes
).toContain("hidden");
2541 // set entropy to have some filtered content
2542 driver
.findElement(By
.css('.entropy'))
2543 .sendKeys("10000000 zxcvbn 00000000 00000000 00000000");
2544 driver
.sleep(entropyFeedbackDelay
).then(function() {
2545 // check the filter warning shows
2546 driver
.findElement(By
.css('.entropy-container .filter-warning'))
2547 .getAttribute("class")
2548 .then(function(classes
) {
2549 expect(classes
).not
.toContain("hidden");
2557 // Bitcoin Cash address can be set to use bitpay format
2558 it('Can use bitpay format for bitcoin cash addresses', function(done
) {
2559 driver
.executeScript(function() {
2560 $(".use-bitpay-addresses").prop("checked", true);
2562 driver
.findElement(By
.css('.phrase'))
2563 .sendKeys("abandon abandon ability");
2564 selectNetwork("BCH - Bitcoin Cash");
2565 driver
.sleep(generateDelay
).then(function() {
2566 getFirstAddress(function(address
) {
2567 expect(address
).toBe("CZnpA9HPmvhuhLLPWJP8rNDpLUYXy1LXFk");