(function() { // mnemonics is populated as required by getLanguage var mnemonics = { "english": new Mnemonic("english") }; var mnemonic = mnemonics["english"]; var seed = null var bip32RootKey = null; var bip32ExtendedKey = null; var network = bitcoin.networks.bitcoin; var addressRowTemplate = $("#address-row-template"); var showIndex = true; var showAddress = true; var showPrivKey = true; var phraseChangeTimeoutEvent = null; var rootKeyChangedTimeoutEvent = null; var DOM = {}; DOM.network = $(".network"); DOM.phraseNetwork = $("#network-phrase"); DOM.phrase = $(".phrase"); DOM.passphrase = $(".passphrase"); DOM.generate = $(".generate"); DOM.seed = $(".seed"); DOM.rootKey = $(".root-key"); DOM.extendedPrivKey = $(".extended-priv-key"); DOM.extendedPubKey = $(".extended-pub-key"); DOM.bip32tab = $("#bip32-tab"); DOM.bip44tab = $("#bip44-tab"); DOM.bip32panel = $("#bip32"); DOM.bip44panel = $("#bip44"); 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.hardenedAddresses = $(".hardened-addresses"); 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"); DOM.languages = $(".languages a"); function init() { // Events DOM.network.on("change", networkChanged); DOM.phrase.on("input", delayedPhraseChanged); DOM.passphrase.on("input", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); 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); DOM.languages.on("click", languageChanged); disableForms(); hidePending(); hideValidationError(); populateNetworkSelect(); } // Event handlers function networkChanged(e) { var networkIndex = e.target.value; networks[networkIndex].onSelect(); if (seed != null) { phraseChanged(); } else { rootKeyChanged(); } } function delayedPhraseChanged() { hideValidationError(); showPending(); if (phraseChangeTimeoutEvent != null) { clearTimeout(phraseChangeTimeoutEvent); } phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400); } function phraseChanged() { showPending(); hideValidationError(); setMnemonicLanguage(); // Get the mnemonic phrase var phrase = DOM.phrase.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); if (errorText) { showValidationError(errorText); return; } calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } function generateClicked() { clearDisplay(); showPending(); setTimeout(function() { setMnemonicLanguage(); var phrase = generateRandomPhrase(); if (!phrase) { return; } phraseChanged(); }, 50); } function languageChanged() { setTimeout(function() { setMnemonicLanguage(); if (DOM.phrase.val().length > 0) { var newPhrase = convertPhraseToNewLanguage(); DOM.phrase.val(newPhrase); phraseChanged(); } else { DOM.generate.trigger("click"); } }, 50); } function toggleIndexes() { showIndex = !showIndex; $("td.index span").toggleClass("invisible"); } function toggleAddresses() { showAddress = !showAddress; $("td.address span").toggleClass("invisible"); } function togglePrivateKeys() { showPrivKey = !showPrivKey; $("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()); var strength = numWords / 3 * 32; var words = mnemonic.generate(strength); DOM.phrase.val(words); return words; } function calcBip32RootKeyFromSeed(phrase, passphrase) { seed = mnemonic.toSeed(phrase, passphrase); bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network); } function calcBip32RootKeyFromBase58(rootKeyBase58) { bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58, network); } function calcBip32ExtendedKey(path) { bip32ExtendedKey = bip32RootKey; // Derive the key from the path var pathBits = path.split("/"); for (var i=0; i 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 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 displayBip32Info() { // Display the key DOM.seed.val(seed); 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; } } displayAddresses(start, rowsToAdd); } function clearDisplay() { clearAddressesList(); clearKey(); hideValidationError(); } function clearAddressesList() { DOM.addresses.empty(); } function clearKey() { DOM.rootKey.val(""); DOM.extendedPrivKey.val(""); DOM.extendedPubKey.val(""); } function addAddressToList(indexText, address, privkey) { var row = $(addressRowTemplate.html()); // Elements var indexCell = row.find(".index span"); var addressCell = row.find(".address span"); var privkeyCell = row.find(".privkey span"); // Content indexCell.text(indexText); addressCell.text(address); privkeyCell.text(privkey); // Visibility if (!showIndex) { indexCell.addClass("invisible"); } if (!showAddress) { addressCell.addClass("invisible"); } if (!showPrivKey) { privkeyCell.addClass("invisible"); } DOM.addresses.append(row); } function hasStrongRandom() { return 'crypto' in window && window['crypto'] !== null; } function disableForms() { $("form").on("submit", function(e) { e.preventDefault(); }); } function parseIntNoNaN(val, defaultVal) { var v = parseInt(val); if (isNaN(v)) { return defaultVal; } return v; } function showPending() { DOM.feedback .text("Calculating...") .show(); } function findNearestWord(word) { var language = getLanguage(); var words = WORDLISTS[language]; var minDistance = 99; var closestWord = words[0]; for (var i=0; i"); option.attr("value", i); option.text(network.name); DOM.phraseNetwork.append(option); } } function getLanguage() { var defaultLanguage = "english"; // Try to get from existing phrase var language = getLanguageFromPhrase(); // Try to get from url if not from phrase if (language.length == 0) { language = getLanguageFromUrl(); } // Default to English if no other option if (language.length == 0) { language = defaultLanguage; } return language; } function getLanguageFromPhrase(phrase) { // Check if how many words from existing phrase match a language. var language = ""; if (!phrase) { phrase = DOM.phrase.val(); } if (phrase.length > 0) { var words = phraseToWordArray(phrase); var languageMatches = {}; for (l in WORDLISTS) { // Track how many words match in this language languageMatches[l] = 0; for (var i=0; i -1; if (wordInLanguage) { languageMatches[l]++; } } // Find languages with most word matches. // This is made difficult due to commonalities between Chinese // simplified vs traditional. var mostMatches = 0; var mostMatchedLanguages = []; for (var l in languageMatches) { var numMatches = languageMatches[l]; if (numMatches > mostMatches) { mostMatches = numMatches; mostMatchedLanguages = [l]; } else if (numMatches == mostMatches) { mostMatchedLanguages.push(l); } } } if (mostMatchedLanguages.length > 0) { // Use first language and warn if multiple detected language = mostMatchedLanguages[0]; if (mostMatchedLanguages.length > 1) { console.warn("Multiple possible languages"); console.warn(mostMatchedLanguages); } } } return language; } function getLanguageFromUrl() { return window.location.hash.substring(1); } function setMnemonicLanguage() { var language = getLanguage(); // Load the bip39 mnemonic generator for this language if required if (!(language in mnemonics)) { mnemonics[language] = new Mnemonic(language); } mnemonic = mnemonics[language]; } function convertPhraseToNewLanguage() { var oldLanguage = getLanguageFromPhrase(); var newLanguage = getLanguageFromUrl(); var oldPhrase = DOM.phrase.val(); var oldWords = phraseToWordArray(oldPhrase); var newWords = []; for (var i=0; i 0) { noBlanks.push(word); } } return noBlanks; } // TODO look at jsbip39 - mnemonic.joinWords function wordArrayToPhrase(words) { var phrase = words.join(" "); var language = getLanguageFromPhrase(phrase); if (language == "japanese") { phrase = words.join("\u3000"); } return phrase; } var networks = [ { name: "Bitcoin", onSelect: function() { network = bitcoin.networks.bitcoin; DOM.bip44coin.val(0); }, }, { name: "Bitcoin Testnet", onSelect: function() { network = bitcoin.networks.testnet; DOM.bip44coin.val(1); }, }, { name: "Litecoin", onSelect: function() { network = bitcoin.networks.litecoin; DOM.bip44coin.val(2); }, }, { name: "Dogecoin", onSelect: function() { network = bitcoin.networks.dogecoin; DOM.bip44coin.val(3); }, }, { name: "ShadowCash", onSelect: function() { network = bitcoin.networks.shadow; DOM.bip44coin.val(35); }, }, { name: "ShadowCash Testnet", onSelect: function() { network = bitcoin.networks.shadowtn; DOM.bip44coin.val(1); }, }, { name: "Viacoin", onSelect: function() { network = bitcoin.networks.viacoin; DOM.bip44coin.val(14); }, }, { name: "Viacoin Testnet", onSelect: function() { network = bitcoin.networks.viacointestnet; DOM.bip44coin.val(1); }, }, { name: "Jumbucks", onSelect: function() { network = bitcoin.networks.jumbucks; DOM.bip44coin.val(26); }, }, { name: "CLAM", onSelect: function() { network = bitcoin.networks.clam; DOM.bip44coin.val(23); }, }, { name: "DASH", onSelect: function() { network = bitcoin.networks.dash; DOM.bip44coin.val(5); }, }, { name: "Namecoin", onSelect: function() { network = bitcoin.networks.namecoin; DOM.bip44coin.val(7); }, }, { name: "Peercoin", onSelect: function() { network = bitcoin.networks.peercoin; DOM.bip44coin.val(6); }, }, ] init(); })();