From efe41586705d3eba480da31bb1002c4e54e73ef0 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 10:31:39 +1000 Subject: [PATCH 1/9] BIP32 Root Key can be specified by user --- bip39-standalone.html | 87 +++++++++++++++++++++++++++++++++++++------ src/index.html | 2 +- src/js/index.js | 85 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/bip39-standalone.html b/bip39-standalone.html index b1fe90e..9d0de5e 100644 --- a/bip39-standalone.html +++ b/bip39-standalone.html @@ -109,7 +109,7 @@
- +
@@ -14830,6 +14830,7 @@ var Mnemonic = function(language) { var showPrivKey = true; var phraseChangeTimeoutEvent = null; + var rootKeyChangedTimeoutEvent = null; var DOM = {}; DOM.network = $(".network"); @@ -14868,12 +14869,13 @@ 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.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); @@ -14888,7 +14890,7 @@ var Mnemonic = function(language) { function networkChanged(e) { var network = e.target.value; networks[network].onSelect(); - delayedPhraseChanged(); + displayBip32Info(); } function delayedPhraseChanged() { @@ -14905,12 +14907,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 +14965,7 @@ var Mnemonic = function(language) { showValidationError(errorText); return; } - // Calculate and display - calcBip32Seed(phrase, passphrase, derivationPath); + calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } @@ -14966,9 +15012,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 +15084,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); diff --git a/src/index.html b/src/index.html index 6708675..fcdf109 100644 --- a/src/index.html +++ b/src/index.html @@ -105,7 +105,7 @@
- +
diff --git a/src/js/index.js b/src/js/index.js index f3582b0..88e891c 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"); @@ -50,12 +51,13 @@ 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.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); @@ -70,7 +72,7 @@ function networkChanged(e) { var network = e.target.value; networks[network].onSelect(); - delayedPhraseChanged(); + displayBip32Info(); } function delayedPhraseChanged() { @@ -87,12 +89,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 +147,7 @@ showValidationError(errorText); return; } - // Calculate and display - calcBip32Seed(phrase, passphrase, derivationPath); + calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } @@ -148,9 +194,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 +266,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); From 146e089e28fbda936955ce74fc1f2214418633c5 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 11:07:37 +1000 Subject: [PATCH 2/9] Hardened Addresses checkbox This closes issue #25 --- src/index.html | 7 +++++++ src/js/index.js | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index fcdf109..25e30fc 100644 --- a/src/index.html +++ b/src/index.html @@ -186,6 +186,13 @@ +
+
+ +
diff --git a/src/js/index.js b/src/js/index.js index 88e891c..68f1cda 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -35,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"); @@ -58,6 +59,7 @@ 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); @@ -362,16 +364,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) } From 8786746ba4b11e6d459e80868cbb2ac5bde4114a Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 11:14:41 +1000 Subject: [PATCH 3/9] Bitcoin Core derivation path information --- src/index.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/index.html b/src/index.html index 25e30fc..15fb063 100644 --- a/src/index.html +++ b/src/index.html @@ -211,6 +211,15 @@

+
+ +
+

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

+
+
From 88e2cdaa545ad7f7fac0256e33e5aeeb6276bc34 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 14:59:47 +1000 Subject: [PATCH 4/9] Test suite using phantomjs --- readme.md | 10 +++ tests.js | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 tests.js 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/tests.js b/tests.js new file mode 100644 index 0000000..12b779e --- /dev/null +++ b/tests.js @@ -0,0 +1,207 @@ +// 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").val(expectedLength); + }); + // 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(); + } + }, 200); + next(); +}); +}, + +// TODO finish these tests + +// Passphrase can be set +// Network can be set to bitcoin testnet +// Network can be set to litecoin +// Network can be set to dogecoin +// Network can be set to shadowcash +// Network can be set to shadowcash testnet +// Network can be set to viacoin +// Network can be set to viacoin testnet +// Network can be set to jumbucks +// Network can be set to clam +// BIP39 seed is set from phrase +// BIP32 root key is set from phrase + +// 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 clears the existing phrase, passphrase and seed +// 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(); From 1975bfbc2be03063046b24cb599c119675264186 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 15:00:19 +1000 Subject: [PATCH 5/9] Standalone has hardened addresses checkbox --- bip39-standalone.html | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/bip39-standalone.html b/bip39-standalone.html index 9d0de5e..5230003 100644 --- a/bip39-standalone.html +++ b/bip39-standalone.html @@ -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 +

+
+
@@ -14853,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"); @@ -14876,6 +14893,7 @@ var Mnemonic = function(language) { 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); @@ -15180,16 +15198,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) } From 54563907c721a90f8fc9861a62fe7c214b5c5041 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 16:16:41 +1000 Subject: [PATCH 6/9] Tests for passphrase and coin selection --- bip39-standalone.html | 27 ++++++++++------- src/index.html | 16 +++++----- src/js/index.js | 11 +++++-- tests.js | 68 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 92 insertions(+), 30 deletions(-) diff --git a/bip39-standalone.html b/bip39-standalone.html index 5230003..5f9cf7c 100644 --- a/bip39-standalone.html +++ b/bip39-standalone.html @@ -71,14 +71,14 @@
@@ -14906,9 +14906,14 @@ var Mnemonic = function(language) { // Event handlers function networkChanged(e) { - var network = e.target.value; - networks[network].onSelect(); - displayBip32Info(); + var networkIndex = e.target.value; + networks[networkIndex].onSelect(); + if (seed != null) { + phraseChanged(); + } + else { + rootKeyChanged(); + } } function delayedPhraseChanged() { diff --git a/src/index.html b/src/index.html index 15fb063..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 68f1cda..8f7d658 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -72,9 +72,14 @@ // Event handlers function networkChanged(e) { - var network = e.target.value; - networks[network].onSelect(); - displayBip32Info(); + var networkIndex = e.target.value; + networks[networkIndex].onSelect(); + if (seed != null) { + phraseChanged(); + } + else { + rootKeyChanged(); + } } function delayedPhraseChanged() { diff --git a/tests.js b/tests.js index 12b779e..7a3a842 100644 --- a/tests.js +++ b/tests.js @@ -111,9 +111,10 @@ page.open(url, function(status) { function() { page.open(url, function(status) { // set the length to 6 - var expectedLength = 6; + var expectedLength = "6"; page.evaluate(function() { - $(".strength").val(expectedLength); + $(".strength option[selected]").removeAttr("selected"); + $(".strength option[value=6]").prop("selected", true); }); // press the 'generate' button page.evaluate(function() { @@ -131,15 +132,65 @@ page.open(url, function(status) { console.log("Actual: " + actualLength); fail(); } - }, 200); - next(); + 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 passphrase + 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); }); }, // TODO finish these tests - -// Passphrase can be set -// Network can be set to bitcoin testnet // Network can be set to litecoin // Network can be set to dogecoin // Network can be set to shadowcash @@ -180,7 +231,8 @@ page.open(url, function(status) { // Additional addresses are shown in order of derivation path // BIP32 root key can be set by the user -// Setting BIP32 clears the existing phrase, passphrase and seed +// 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 From 59193779ba2860708e05b7db6751718d3b946efe Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 16:28:53 +1000 Subject: [PATCH 7/9] Tests for all other coins / networks --- tests.js | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 2 deletions(-) diff --git a/tests.js b/tests.js index 7a3a842..352f225 100644 --- a/tests.js +++ b/tests.js @@ -165,7 +165,7 @@ page.open(url, function(status) { // Network can be set to bitcoin testnet function() { page.open(url, function(status) { - // set the phrase and passphrase + // set the phrase and coin var expected = "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi"; page.evaluate(function() { $(".phrase").val("abandon abandon ability"); @@ -190,15 +190,231 @@ page.open(url, function(status) { }); }, -// TODO finish these tests // 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); +}); +}, + +// TODO finish these tests // BIP39 seed is set from phrase // BIP32 root key is set from phrase From c196ad55d911aff4458d3ae582b33fc22e06a217 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 16:32:21 +1000 Subject: [PATCH 8/9] Test bip39 seed value as generated from mnemonic --- tests.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests.js b/tests.js index 352f225..e534532 100644 --- a/tests.js +++ b/tests.js @@ -414,8 +414,31 @@ page.open(url, function(status) { }); }, -// TODO finish these tests // 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 // Tabs show correct addresses when changed From ec60b6624a5f84943ffb6e6be8b3af9c2274097b Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 16:32:42 +1000 Subject: [PATCH 9/9] Test bip32 root key as generated from mnemonic --- tests.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests.js b/tests.js index e534532..f5ce6d3 100644 --- a/tests.js +++ b/tests.js @@ -440,6 +440,31 @@ page.open(url, function(status) { }, // 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