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 @@
+
+
@@ -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 @@
+
+
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();