diff --git a/bip39-standalone.html b/bip39-standalone.html index b1fe90e..5f9cf7c 100644 --- a/bip39-standalone.html +++ b/bip39-standalone.html @@ -71,14 +71,14 @@
@@ -109,7 +109,7 @@
- +
@@ -190,6 +190,13 @@
+
+
+ +
@@ -208,6 +215,15 @@

+
+ +
+

+ Use path m/0'/0' with hardened addresses. + For more info see the Bitcoin Core BIP32 implementation +

+
+
@@ -14830,6 +14846,7 @@ var Mnemonic = function(language) { var showPrivKey = true; var phraseChangeTimeoutEvent = null; + var rootKeyChangedTimeoutEvent = null; var DOM = {}; DOM.network = $(".network"); @@ -14852,6 +14869,7 @@ var Mnemonic = function(language) { DOM.bip44account = $("#bip44 .account"); DOM.bip44change = $("#bip44 .change"); DOM.strength = $(".strength"); + DOM.hardenedAddresses = $(".hardened-addresses"); DOM.addresses = $(".addresses"); DOM.rowsToAdd = $(".rows-to-add"); DOM.more = $(".more"); @@ -14868,12 +14886,14 @@ var Mnemonic = function(language) { DOM.passphrase.on("input", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); - DOM.bip32path.on("input", delayedPhraseChanged); - DOM.bip44purpose.on("input", delayedPhraseChanged); - DOM.bip44coin.on("input", delayedPhraseChanged); - DOM.bip44account.on("input", delayedPhraseChanged); - DOM.bip44change.on("input", delayedPhraseChanged); - DOM.tab.on("click", delayedPhraseChanged); + DOM.rootKey.on("input", delayedRootKeyChanged); + DOM.bip32path.on("input", calcForDerivationPath); + DOM.bip44purpose.on("input", calcForDerivationPath); + DOM.bip44coin.on("input", calcForDerivationPath); + DOM.bip44account.on("input", calcForDerivationPath); + DOM.bip44change.on("input", calcForDerivationPath); + DOM.tab.on("shown.bs.tab", calcForDerivationPath); + DOM.hardenedAddresses.on("change", calcForDerivationPath); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); @@ -14886,9 +14906,14 @@ var Mnemonic = function(language) { // Event handlers function networkChanged(e) { - var network = e.target.value; - networks[network].onSelect(); - delayedPhraseChanged(); + var networkIndex = e.target.value; + networks[networkIndex].onSelect(); + if (seed != null) { + phraseChanged(); + } + else { + rootKeyChanged(); + } } function delayedPhraseChanged() { @@ -14905,12 +14930,57 @@ var Mnemonic = function(language) { hideValidationError(); // Get the mnemonic phrase var phrase = DOM.phrase.val(); - var passphrase = DOM.passphrase.val(); var errorText = findPhraseErrors(phrase); if (errorText) { showValidationError(errorText); return; } + // Calculate and display + var passphrase = DOM.passphrase.val(); + calcBip32RootKeyFromSeed(phrase, passphrase); + calcForDerivationPath(); + hidePending(); + } + + function delayedRootKeyChanged() { + // Warn if there is an existing mnemonic or passphrase. + if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) { + if (!confirm("This will clear existing mnemonic and passphrase")) { + DOM.rootKey.val(bip32RootKey); + return + } + } + hideValidationError(); + showPending(); + // Clear existing mnemonic and passphrase + DOM.phrase.val(""); + DOM.passphrase.val(""); + seed = null; + if (rootKeyChangedTimeoutEvent != null) { + clearTimeout(rootKeyChangedTimeoutEvent); + } + rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400); + } + + function rootKeyChanged() { + showPending(); + hideValidationError(); + // Validate the root key TODO + var rootKeyBase58 = DOM.rootKey.val(); + var errorText = validateRootKey(rootKeyBase58); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + calcBip32RootKeyFromBase58(rootKeyBase58); + calcForDerivationPath(); + hidePending(); + } + + function calcForDerivationPath() { + showPending(); + hideValidationError(); // Get the derivation path var derivationPath = getDerivationPath(); var errorText = findDerivationPathErrors(derivationPath); @@ -14918,8 +14988,7 @@ var Mnemonic = function(language) { showValidationError(errorText); return; } - // Calculate and display - calcBip32Seed(phrase, passphrase, derivationPath); + calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } @@ -14966,9 +15035,16 @@ var Mnemonic = function(language) { return words; } - function calcBip32Seed(phrase, passphrase, path) { + function calcBip32RootKeyFromSeed(phrase, passphrase) { seed = mnemonic.toSeed(phrase, passphrase); bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network); + } + + function calcBip32RootKeyFromBase58(rootKeyBase58) { + bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58); + } + + function calcBip32ExtendedKey(path) { bip32ExtendedKey = bip32RootKey; // Derive the key from the path var pathBits = path.split("/"); @@ -15031,6 +15107,16 @@ var Mnemonic = function(language) { return false; } + function validateRootKey(rootKeyBase58) { + try { + bitcoin.HDNode.fromBase58(rootKeyBase58); + } + catch (e) { + return "Invalid root key"; + } + return ""; + } + function getDerivationPath() { if (DOM.bip44tab.hasClass("active")) { var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44); @@ -15117,16 +15203,27 @@ var Mnemonic = function(language) { function TableRow(index) { + var useHardenedAddresses = DOM.hardenedAddresses.prop("checked"); + function init() { calculateValues(); } function calculateValues() { setTimeout(function() { - var key = bip32ExtendedKey.derive(index); + var key = ""; + if (useHardenedAddresses) { + key = bip32ExtendedKey.deriveHardened(index); + } + else { + key = bip32ExtendedKey.derive(index); + } var address = key.getAddress().toString(); var privkey = key.privKey.toWIF(network); var indexText = getDerivationPath() + "/" + index; + if (useHardenedAddresses) { + indexText = indexText + "'"; + } addAddressToList(indexText, address, privkey); }, 50) } diff --git a/readme.md b/readme.md index 210703b..02e1e18 100644 --- a/readme.md +++ b/readme.md @@ -34,3 +34,13 @@ Please do not make modifications to `bip39-standalone.html`, since they will be overwritten by `compile.py`. Make changes in `src/*` and apply them using the command `python compile.py` + +# Tests + +Tests depend on [phantomjs](http://phantomjs.org/). + +Run tests from the command-line + +``` +$ phantomjs tests.js +``` diff --git a/src/index.html b/src/index.html index 6708675..ea7096c 100644 --- a/src/index.html +++ b/src/index.html @@ -67,14 +67,14 @@
@@ -105,7 +105,7 @@
- +
@@ -186,6 +186,13 @@
+
+
+ +
@@ -204,6 +211,15 @@

+
+ +
+

+ Use path m/0'/0' with hardened addresses. + For more info see the Bitcoin Core BIP32 implementation +

+
+
diff --git a/src/js/index.js b/src/js/index.js index f3582b0..8f7d658 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -12,6 +12,7 @@ var showPrivKey = true; var phraseChangeTimeoutEvent = null; + var rootKeyChangedTimeoutEvent = null; var DOM = {}; DOM.network = $(".network"); @@ -34,6 +35,7 @@ DOM.bip44account = $("#bip44 .account"); DOM.bip44change = $("#bip44 .change"); DOM.strength = $(".strength"); + DOM.hardenedAddresses = $(".hardened-addresses"); DOM.addresses = $(".addresses"); DOM.rowsToAdd = $(".rows-to-add"); DOM.more = $(".more"); @@ -50,12 +52,14 @@ DOM.passphrase.on("input", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); - DOM.bip32path.on("input", delayedPhraseChanged); - DOM.bip44purpose.on("input", delayedPhraseChanged); - DOM.bip44coin.on("input", delayedPhraseChanged); - DOM.bip44account.on("input", delayedPhraseChanged); - DOM.bip44change.on("input", delayedPhraseChanged); - DOM.tab.on("click", delayedPhraseChanged); + DOM.rootKey.on("input", delayedRootKeyChanged); + DOM.bip32path.on("input", calcForDerivationPath); + DOM.bip44purpose.on("input", calcForDerivationPath); + DOM.bip44coin.on("input", calcForDerivationPath); + DOM.bip44account.on("input", calcForDerivationPath); + DOM.bip44change.on("input", calcForDerivationPath); + DOM.tab.on("shown.bs.tab", calcForDerivationPath); + DOM.hardenedAddresses.on("change", calcForDerivationPath); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); @@ -68,9 +72,14 @@ // Event handlers function networkChanged(e) { - var network = e.target.value; - networks[network].onSelect(); - delayedPhraseChanged(); + var networkIndex = e.target.value; + networks[networkIndex].onSelect(); + if (seed != null) { + phraseChanged(); + } + else { + rootKeyChanged(); + } } function delayedPhraseChanged() { @@ -87,12 +96,57 @@ hideValidationError(); // Get the mnemonic phrase var phrase = DOM.phrase.val(); - var passphrase = DOM.passphrase.val(); var errorText = findPhraseErrors(phrase); if (errorText) { showValidationError(errorText); return; } + // Calculate and display + var passphrase = DOM.passphrase.val(); + calcBip32RootKeyFromSeed(phrase, passphrase); + calcForDerivationPath(); + hidePending(); + } + + function delayedRootKeyChanged() { + // Warn if there is an existing mnemonic or passphrase. + if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) { + if (!confirm("This will clear existing mnemonic and passphrase")) { + DOM.rootKey.val(bip32RootKey); + return + } + } + hideValidationError(); + showPending(); + // Clear existing mnemonic and passphrase + DOM.phrase.val(""); + DOM.passphrase.val(""); + seed = null; + if (rootKeyChangedTimeoutEvent != null) { + clearTimeout(rootKeyChangedTimeoutEvent); + } + rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400); + } + + function rootKeyChanged() { + showPending(); + hideValidationError(); + // Validate the root key TODO + var rootKeyBase58 = DOM.rootKey.val(); + var errorText = validateRootKey(rootKeyBase58); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + calcBip32RootKeyFromBase58(rootKeyBase58); + calcForDerivationPath(); + hidePending(); + } + + function calcForDerivationPath() { + showPending(); + hideValidationError(); // Get the derivation path var derivationPath = getDerivationPath(); var errorText = findDerivationPathErrors(derivationPath); @@ -100,8 +154,7 @@ showValidationError(errorText); return; } - // Calculate and display - calcBip32Seed(phrase, passphrase, derivationPath); + calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } @@ -148,9 +201,16 @@ return words; } - function calcBip32Seed(phrase, passphrase, path) { + function calcBip32RootKeyFromSeed(phrase, passphrase) { seed = mnemonic.toSeed(phrase, passphrase); bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network); + } + + function calcBip32RootKeyFromBase58(rootKeyBase58) { + bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58); + } + + function calcBip32ExtendedKey(path) { bip32ExtendedKey = bip32RootKey; // Derive the key from the path var pathBits = path.split("/"); @@ -213,6 +273,16 @@ return false; } + function validateRootKey(rootKeyBase58) { + try { + bitcoin.HDNode.fromBase58(rootKeyBase58); + } + catch (e) { + return "Invalid root key"; + } + return ""; + } + function getDerivationPath() { if (DOM.bip44tab.hasClass("active")) { var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44); @@ -299,16 +369,27 @@ function TableRow(index) { + var useHardenedAddresses = DOM.hardenedAddresses.prop("checked"); + function init() { calculateValues(); } function calculateValues() { setTimeout(function() { - var key = bip32ExtendedKey.derive(index); + var key = ""; + if (useHardenedAddresses) { + key = bip32ExtendedKey.deriveHardened(index); + } + else { + key = bip32ExtendedKey.derive(index); + } var address = key.getAddress().toString(); var privkey = key.privKey.toWIF(network); var indexText = getDerivationPath() + "/" + index; + if (useHardenedAddresses) { + indexText = indexText + "'"; + } addAddressToList(indexText, address, privkey); }, 50) } diff --git a/tests.js b/tests.js new file mode 100644 index 0000000..f5ce6d3 --- /dev/null +++ b/tests.js @@ -0,0 +1,523 @@ +// Usage: +// $ phantomjs tests.js + + +var page = require('webpage').create(); +var url = 'src/index.html'; + +page.onResourceError = function(e) { + console.log("Error loading " + e.url); + phantom.exit(); +} + +function fail() { + console.log("Failed"); + phantom.exit(); +} + +function next() { + if (tests.length > 0) { + var testsStr = tests.length == 1 ? "test" : "tests"; + console.log(tests.length + " " + testsStr + " remaining"); + tests.shift()(); + } + else { + console.log("Finished with 0 failures"); + phantom.exit(); + } +} + +tests = [ + +// Page loads with status of 'success' +function() { +page.open(url, function(status) { + if (status != "success") { + console.log("Page did not load with status 'success'"); + fail(); + } + next(); +}); +}, + +// Page has text +function() { +page.open(url, function(status) { + var content = page.evaluate(function() { + return document.body.textContent.trim(); + }); + if (!content) { + console.log("Page does not have text"); + fail(); + } + next(); +}); +}, + +// Entering mnemonic generates addresses +function() { +page.open(url, function(status) { + var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug"; + // set the phrase + page.evaluate(function() { + $(".phrase").val("abandon abandon ability").trigger("input"); + }); + // get the address + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Mnemonic did not generate address"); + console.log("Expected: " + expected); + console.log("Got: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Random button generates random mnemonic +function() { +page.open(url, function(status) { + // check initial phrase is empty + var phrase = page.evaluate(function() { + return $(".phrase").text(); + }); + if (phrase != "") { + console.log("Initial phrase is not blank"); + fail(); + } + // press the 'generate' button + page.evaluate(function() { + $(".generate").click(); + }); + // get the new phrase + setTimeout(function() { + var phrase = page.evaluate(function() { + return $(".phrase").val(); + }); + if (phrase.length <= 0) { + console.log("Phrase not generated by pressing button"); + fail(); + } + next(); + }, 1000); +}); +}, + +// Mnemonic length can be customized +function() { +page.open(url, function(status) { + // set the length to 6 + var expectedLength = "6"; + page.evaluate(function() { + $(".strength option[selected]").removeAttr("selected"); + $(".strength option[value=6]").prop("selected", true); + }); + // press the 'generate' button + page.evaluate(function() { + $(".generate").click(); + }); + // check the new phrase is six words long + setTimeout(function() { + var actualLength = page.evaluate(function() { + var words = $(".phrase").val().split(" "); + return words.length; + }); + if (actualLength != expectedLength) { + console.log("Phrase not generated with correct length"); + console.log("Expected: " + expectedLength); + console.log("Actual: " + actualLength); + fail(); + } + next(); + }, 1000); +}); +}, + +// Passphrase can be set +function() { +page.open(url, function(status) { + // set the phrase and passphrase + var expected = "15pJzUWPGzR7avffV9nY5by4PSgSKG9rba"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".passphrase").val("secure_passphrase").trigger("input"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Passphrase results in wrong address"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to bitcoin testnet +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=1]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Bitcoin testnet address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to litecoin +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=2]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Litecoin address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to dogecoin +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=3]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Dogecoin address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to shadowcash +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=4]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Shadowcash address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to shadowcash testnet +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=5]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Shadowcash testnet address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to viacoin +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=6]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Viacoin address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to viacoin testnet +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=7]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Viacoin testnet address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to jumbucks +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=8]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("Jumbucks address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// Network can be set to clam +function() { +page.open(url, function(status) { + // set the phrase and coin + var expected = "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + $(".network option[selected]").removeAttr("selected"); + $(".network option[value=9]").prop("selected", true); + $(".network").trigger("change"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".address:first").text(); + }); + if (actual != expected) { + console.log("CLAM address is incorrect"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// BIP39 seed is set from phrase +function() { +page.open(url, function(status) { + // set the phrase + var expected = "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".seed").val(); + }); + if (actual != expected) { + console.log("BIP39 seed is incorrectly generated from mnemonic"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// BIP32 root key is set from phrase +function() { +page.open(url, function(status) { + // set the phrase + var expected = "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi"; + page.evaluate(function() { + $(".phrase").val("abandon abandon ability"); + $(".phrase").trigger("input"); + }); + // check the address is generated correctly + setTimeout(function() { + var actual = page.evaluate(function() { + return $(".root-key").val(); + }); + if (actual != expected) { + console.log("Root key is incorrectly generated from mnemonic"); + console.log("Expected: " + expected); + console.log("Actual: " + actual); + fail(); + } + next(); + }, 1000); +}); +}, + +// TODO finish these tests + +// Tabs show correct addresses when changed + +// BIP44 derivation path is shown +// BIP44 extended private key is shown +// BIP44 extended public key is shown +// BIP44 purpose field changes address list +// BIP44 coin field changes address list +// BIP44 account field changes address list +// BIP44 external/internal field changes address list + +// BIP32 derivation path can be set +// BIP32 can use hardened derivation paths +// BIP32 extended private key is shown +// BIP32 extended public key is shown + +// Derivation path is shown in table +// Derivation path for address can be hardened +// Derivation path visibility can be toggled +// Address is shown +// Addresses are shown in order of derivation path +// Address visibility can be toggled +// Private key is shown +// Private key visibility can be toggled + +// More addresses can be generated +// A custom number of additional addresses can be generated +// Additional addresses are shown in order of derivation path + +// BIP32 root key can be set by the user +// Setting BIP32 root key clears the existing phrase, passphrase and seed +// Clearing of phrase, passphrase and seed can be cancelled by user +// Custom BIP32 root key is used when changing the derivation path + +// Incorrect mnemonic shows error +// Incorrect word shows suggested replacement +// Incorrect BIP32 root key shows error +// Derivation path not starting with m shows error +// Derivation path containing invalid characters shows useful error + +// Github Issue 11: Default word length is 15 +// https://github.com/dcpos/bip39/issues/11 + +// Github Issue 12: Generate more rows with private keys hidden +// https://github.com/dcpos/bip39/issues/12 + +// Github Issue 19: Mnemonic is not sensitive to whitespace +// https://github.com/dcpos/bip39/issues/19 + +// Github Issue 23: Use correct derivation path when changing tabs +// https://github.com/dcpos/bip39/issues/23 + +]; + +console.log("Running tests..."); +next();