(function() { var mnemonic = new Mnemonic("english"); var bip32RootKey = null; var bip32ExtendedKey = null; var network = Bitcoin.networks.bitcoin; var addressRowTemplate = $("#address-row-template"); var phraseChangeTimeoutEvent = null; var DOM = {}; DOM.phrase = $(".phrase"); DOM.generate = $(".generate"); DOM.rootKey = $(".root-key"); DOM.extendedPrivKey = $(".extended-priv-key"); DOM.extendedPubKey = $(".extended-pub-key"); DOM.bip32path = $("#bip32-path"); DOM.bip44path = $("#bip44-path"); DOM.bip44purpose = $("#bip44 .purpose"); DOM.bip44coin = $("#bip44 .coin"); DOM.bip44account = $("#bip44 .account"); DOM.bip44change = $("#bip44 .change"); DOM.strength = $(".strength"); DOM.addresses = $(".addresses"); DOM.rowsToAdd = $(".rows-to-add"); DOM.more = $(".more"); DOM.feedback = $(".feedback"); DOM.tab = $(".derivation-type a"); DOM.indexToggle = $(".index-toggle"); DOM.addressToggle = $(".address-toggle"); DOM.privateKeyToggle = $(".private-key-toggle"); var derivationPath = DOM.bip44path.val(); function init() { // Events DOM.phrase.on("keyup", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); DOM.bip32path.on("keyup", bip32Changed); DOM.bip44purpose.on("keyup", bip44Changed); DOM.bip44coin.on("keyup", bip44Changed); DOM.bip44account.on("keyup", bip44Changed); DOM.bip44change.on("keyup", bip44Changed); DOM.tab.on("click", tabClicked); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); disableForms(); hidePending(); hideValidationError(); } // Event handlers function delayedPhraseChanged() { hideValidationError(); showPending(); if (phraseChangeTimeoutEvent != null) { clearTimeout(phraseChangeTimeoutEvent); } phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400); } function phraseChanged() { showPending(); hideValidationError(); // Get the mnemonic phrase var phrase = DOM.phrase.val(); var errorText = findPhraseErrors(phrase); if (errorText) { showValidationError(errorText); return; } // Get the derivation path var errorText = findDerivationPathErrors(); if (errorText) { showValidationError(errorText); return; } // Calculate and display calcBip32Seed(phrase, derivationPath); displayBip32Info(); hidePending(); } function generateClicked() { clearDisplay(); showPending(); setTimeout(function() { var phrase = generateRandomPhrase(); if (!phrase) { return; } phraseChanged(); }, 50); } function tabClicked(e) { var activePath = $(e.target.getAttribute("href") + " .path"); derivationPath = activePath.val(); derivationChanged(); } function derivationChanged() { delayedPhraseChanged(); } function bip32Changed() { derivationPath = DOM.bip32path.val(); derivationChanged(); } function bip44Changed() { setBip44DerivationPath(); derivationPath = DOM.bip44path.val(); derivationChanged(); } function toggleIndexes() { $("td.index span").toggleClass("invisible"); } function toggleAddresses() { $("td.address span").toggleClass("invisible"); } function togglePrivateKeys() { $("td.privkey span").toggleClass("invisible"); } // Private methods function generateRandomPhrase() { if (!hasStrongRandom()) { var errorText = "This browser does not support strong randomness"; showValidationError(errorText); return; } var numWords = parseInt(DOM.strength.val()); // Check strength is an integer if (isNaN(numWords)) { DOM.strength.val("12"); numWords = 12; } // Check strength is a multiple of 32, if not round it down if (numWords % 3 != 0) { numWords = Math.floor(numWords / 3) * 3; DOM.strength.val(numWords); } // Check strength is at least 32 if (numWords == 0) { numWords = 3; DOM.strength.val(numWords); } var strength = numWords / 3 * 32; var words = mnemonic.generate(strength); DOM.phrase.val(words); return words; } function calcBip32Seed(phrase, path) { var seed = mnemonic.toSeed(phrase); var seedHash = Bitcoin.crypto.sha256(seed).toString("hex"); bip32RootKey = Bitcoin.HDNode.fromSeedHex(seedHash, network); bip32ExtendedKey = bip32RootKey; // Derive the key from the path var pathBits = path.split("/"); for (var i=0; i 0) { // TODO check that lowercasing is always valid to do proper.push(part.toLowerCase()); } } // TODO some levenstein on the words var properPhrase = proper.join(' '); // Check the words are valid var isValid = mnemonic.check(properPhrase); if (!isValid) { return "Invalid mnemonic"; } return false; } function findDerivationPathErrors(path) { // TODO return false; } function displayBip32Info() { // Display the key var rootKey = bip32RootKey.toBase58(); DOM.rootKey.val(rootKey); var extendedPrivKey = bip32ExtendedKey.toBase58(); DOM.extendedPrivKey.val(extendedPrivKey); var extendedPubKey = bip32ExtendedKey.toBase58(false); DOM.extendedPubKey.val(extendedPubKey); // Display the addresses and privkeys clearAddressesList(); displayAddresses(0, 20); } function displayAddresses(start, total) { for (var i=0; i 200) { var msg = "Generating " + rowsToAdd + " rows could take a while. "; msg += "Do you want to continue?"; if (!confirm(msg)) { return; } } showPending(); setTimeout(function() { displayAddresses(start, rowsToAdd); hidePending(); }, 50); } function clearDisplay() { clearAddressesList(); clearKey(); hideValidationError(); } function clearAddressesList() { DOM.addresses.empty(); } function clearKey() { DOM.rootKey.val(""); DOM.extendedPrivKey.val(""); DOM.extendedPubKey.val(""); } function addAddressToList(index, address, privkey) { var row = $(addressRowTemplate.html()); row.find(".index span").text(index); row.find(".address span").text(address); row.find(".privkey span").text(privkey); DOM.addresses.append(row); } function hasStrongRandom() { return 'crypto' in window && window['crypto'] !== null; } function disableForms() { $("form").on("submit", function(e) { e.preventDefault(); }); } function setBip44DerivationPath() { var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44); var coin = parseIntNoNaN(DOM.bip44coin.val(), 0); var account = parseIntNoNaN(DOM.bip44account.val(), 0); var change = parseIntNoNaN(DOM.bip44change.val(), 0); var path = "m/"; path += purpose + "'/"; path += coin + "'/"; path += account + "'/"; path += change; DOM.bip44path.val(path); } function parseIntNoNaN(val, defaultVal) { var v = parseInt(val); if (isNaN(v)) { return defaultVal; } return v; } function showPending() { DOM.feedback .text("Calculating...") .show(); } function hidePending() { DOM.feedback .text("") .hide(); } init(); })();