var showPrivKey = true;
var phraseChangeTimeoutEvent = null;
+ var rootKeyChangedTimeoutEvent = null;
var DOM = {};
DOM.network = $(".network");
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");
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);
// 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() {
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);
showValidationError(errorText);
return;
}
- // Calculate and display
- calcBip32Seed(phrase, passphrase, derivationPath);
+ calcBip32ExtendedKey(derivationPath);
displayBip32Info();
hidePending();
}
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("/");
proper.push(part.toLowerCase());
}
}
- // TODO some levenstein on the words
var properPhrase = proper.join(' ');
+ // Check each word
+ for (var i=0; i<proper.length; i++) {
+ var word = proper[i];
+ if (WORDLISTS["english"].indexOf(word) == -1) {
+ console.log("Finding closest match to " + word);
+ var nearestWord = findNearestWord(word);
+ return word + " not in wordlist, did you mean " + nearestWord + "?";
+ }
+ }
// Check the words are valid
var isValid = mnemonic.check(properPhrase);
if (!isValid) {
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);
}
function findDerivationPathErrors(path) {
- // TODO
+ // TODO is not perfect but is better than nothing
+ // Inspired by
+ // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
+ // and
+ // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
+ var maxDepth = 255; // TODO verify this!!
+ var maxIndexValue = Math.pow(2, 31); // TODO verify this!!
+ if (path[0] != "m") {
+ return "First character must be 'm'";
+ }
+ if (path.length > 1) {
+ if (path[1] != "/") {
+ return "Separator must be '/'";
+ }
+ var indexes = path.split("/");
+ if (indexes.length > maxDepth) {
+ return "Derivation depth is " + indexes.length + ", must be less than " + maxDepth;
+ }
+ for (var depth = 1; depth<indexes.length; depth++) {
+ var index = indexes[depth];
+ var invalidChars = index.replace(/^[0-9]+'?$/g, "")
+ if (invalidChars.length > 0) {
+ return "Invalid characters " + invalidChars + " found at depth " + depth;
+ }
+ var indexValue = parseInt(index.replace("'", ""));
+ if (isNaN(depth)) {
+ return "Invalid number at depth " + depth;
+ }
+ if (indexValue > maxIndexValue) {
+ return "Value of " + indexValue + " at depth " + depth + " must be less than " + maxIndexValue;
+ }
+ }
+ }
return false;
}
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)
}
.show();
}
+ function findNearestWord(word) {
+ var words = WORDLISTS["english"];
+ var minDistance = 99;
+ var closestWord = words[0];
+ for (var i=0; i<words.length; i++) {
+ var comparedTo = words[i];
+ var distance = Levenshtein.get(word, comparedTo);
+ if (distance < minDistance) {
+ closestWord = comparedTo;
+ minDistance = distance;
+ }
+ }
+ return closestWord;
+ }
+
function hidePending() {
DOM.feedback
.text("")