X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FBIP39.git;a=blobdiff_plain;f=src%2Fjs%2Findex.js;h=535cac690658da9e52ab59477da574b53f8d1173;hp=391f40b88049acd1053aed0be293a13d46577dbf;hb=e2186682ac0cfa1f301c226408cdfb438f7b4d6e;hpb=3960807353a4dfd44620d418ac7b2e393dccafd6 diff --git a/src/js/index.js b/src/js/index.js index 391f40b..9578146 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -14,7 +14,7 @@ var showPubKey = true; var showPrivKey = true; var showQr = false; - var litecoinUseLtub = false; + var litecoinUseLtub = true; var entropyChangeTimeoutEvent = null; var phraseChangeTimeoutEvent = null; @@ -23,6 +23,7 @@ var generationProcesses = []; var DOM = {}; + DOM.privacyScreenToggle = $(".privacy-screen-toggle"); DOM.network = $(".network"); DOM.bip32Client = $("#bip32-client"); DOM.phraseNetwork = $("#network-phrase"); @@ -37,7 +38,10 @@ DOM.entropyBitsPerEvent = DOM.entropyContainer.find(".bits-per-event"); DOM.entropyWordCount = DOM.entropyContainer.find(".word-count"); DOM.entropyBinary = DOM.entropyContainer.find(".binary"); + DOM.entropyWordIndexes = DOM.entropyContainer.find(".word-indexes"); + DOM.entropyChecksum = DOM.entropyContainer.find(".checksum"); DOM.entropyMnemonicLength = DOM.entropyContainer.find(".mnemonic-length"); + DOM.entropyWeakEntropyOverrideWarning = DOM.entropyContainer.find(".weak-entropy-override-warning"); DOM.entropyFilterWarning = DOM.entropyContainer.find(".filter-warning"); DOM.phrase = $(".phrase"); DOM.passphrase = $(".passphrase"); @@ -52,6 +56,8 @@ DOM.bip32tab = $("#bip32-tab"); DOM.bip44tab = $("#bip44-tab"); DOM.bip49tab = $("#bip49-tab"); + DOM.bip84tab = $("#bip84-tab"); + DOM.bip141tab = $("#bip141-tab"); DOM.bip32panel = $("#bip32"); DOM.bip44panel = $("#bip44"); DOM.bip49panel = $("#bip49"); @@ -72,14 +78,32 @@ DOM.bip49accountXprv = $("#bip49 .account-xprv"); DOM.bip49accountXpub = $("#bip49 .account-xpub"); DOM.bip49change = $("#bip49 .change"); + DOM.bip84unavailable = $("#bip84 .unavailable"); + DOM.bip84available = $("#bip84 .available"); + DOM.bip84path = $("#bip84-path"); + DOM.bip84purpose = $("#bip84 .purpose"); + DOM.bip84coin = $("#bip84 .coin"); + DOM.bip84account = $("#bip84 .account"); + DOM.bip84accountXprv = $("#bip84 .account-xprv"); + DOM.bip84accountXpub = $("#bip84 .account-xpub"); + DOM.bip84change = $("#bip84 .change"); + DOM.bip141unavailable = $("#bip141 .unavailable"); + DOM.bip141available = $("#bip141 .available"); + DOM.bip141path = $("#bip141-path"); + DOM.bip141semantics = $(".bip141-semantics"); DOM.generatedStrength = $(".generate-container .strength"); + DOM.generatedStrengthWarning = $(".generate-container .warning"); DOM.hardenedAddresses = $(".hardened-addresses"); - DOM.useP2wpkhNestedInP2sh = $(".p2wpkh-nested-in-p2sh"); - DOM.useBitpayAddressesContainer = $(".use-bitpay-addresses-container"); - DOM.useBitpayAddresses = $(".use-bitpay-addresses"); + DOM.bitcoinCashAddressTypeContainer = $(".bch-addr-type-container"); + DOM.bitcoinCashAddressType = $("[name=bch-addr-type]") + DOM.useBip38 = $(".use-bip38"); + DOM.bip38Password = $(".bip38-password"); DOM.addresses = $(".addresses"); + DOM.csvTab = $("#csv-tab a"); + DOM.csv = $(".csv"); DOM.rowsToAdd = $(".rows-to-add"); DOM.more = $(".more"); + DOM.moreRowsStartIndex = $(".more-rows-start-index"); DOM.feedback = $(".feedback"); DOM.tab = $(".derivation-type a"); DOM.indexToggle = $(".index-toggle"); @@ -95,6 +119,8 @@ function init() { // Events + DOM.privacyScreenToggle.on("change", privacyScreenToggled); + DOM.generatedStrength.on("change", generatedStrengthChanged); DOM.network.on("change", networkChanged); DOM.bip32Client.on("change", bip32ClientChanged); DOM.useEntropy.on("change", setEntropyVisibility); @@ -111,15 +137,21 @@ DOM.bip44change.on("input", calcForDerivationPath); DOM.bip49account.on("input", calcForDerivationPath); DOM.bip49change.on("input", calcForDerivationPath); + DOM.bip84account.on("input", calcForDerivationPath); + DOM.bip84change.on("input", calcForDerivationPath); + DOM.bip141path.on("input", calcForDerivationPath); + DOM.bip141semantics.on("change", tabChanged); DOM.tab.on("shown.bs.tab", tabChanged); DOM.hardenedAddresses.on("change", calcForDerivationPath); - DOM.useP2wpkhNestedInP2sh.on("change", calcForDerivationPath); + DOM.useBip38.on("change", calcForDerivationPath); + DOM.bip38Password.on("change", calcForDerivationPath); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.publicKeyToggle.on("click", togglePublicKeys); DOM.privateKeyToggle.on("click", togglePrivateKeys); + DOM.csvTab.on("click", updateCsv); DOM.languages.on("click", languageChanged); - DOM.useBitpayAddresses.on("change", useBitpayAddressesChange); + DOM.bitcoinCashAddressType.on("change", bitcoinCashAddressTypeChange); setQrEvents(DOM.showQrEls); disableForms(); hidePending(); @@ -130,20 +162,25 @@ // Event handlers + function generatedStrengthChanged() { + var strength = parseInt(DOM.generatedStrength.val()); + if (strength < 12) { + DOM.generatedStrengthWarning.removeClass("hidden"); + } + else { + DOM.generatedStrengthWarning.addClass("hidden"); + } + } + function networkChanged(e) { clearDerivedKeys(); clearAddressesList(); DOM.litecoinLtubContainer.addClass("hidden"); - DOM.useBitpayAddressesContainer.addClass("hidden"); + DOM.bitcoinCashAddressTypeContainer.addClass("hidden"); var networkIndex = e.target.value; var network = networks[networkIndex]; network.onSelect(); - if (network.p2wpkhNestedInP2shAvailable) { - showP2wpkhNestedInP2shAvailable(); - } - else { - showP2wpkhNestedInP2shUnavailable(); - } + adjustNetworkForSegwit(); if (seed != null) { phraseChanged(); } @@ -212,11 +249,13 @@ var passphrase = DOM.passphrase.val(); calcBip32RootKeyFromSeed(phrase, passphrase); calcForDerivationPath(); + // Show the word indexes + showWordIndexes(); } function tabChanged() { showPending(); - adjustNetworkForBip49(); + adjustNetworkForSegwit(); var phrase = DOM.phrase.val(); if (phrase != "") { // Calculate and display for mnemonic @@ -317,10 +356,10 @@ function litecoinUseLtubChanged() { litecoinUseLtub = DOM.litecoinUseLtub.prop("checked"); if (litecoinUseLtub) { - network = bitcoinjs.bitcoin.networks.litecoinLtub; + network = bitcoinjs.bitcoin.networks.litecoin; } else { - network = bitcoinjs.bitcoin.networks.litecoin; + network = bitcoinjs.bitcoin.networks.litecoinXprv; } phraseChanged(); } @@ -329,10 +368,13 @@ clearDerivedKeys(); clearAddressesList(); showPending(); - // Don't show bip49 if it's selected but network doesn't support it - if (bip49TabSelected() && !networkHasBip49()) { + // Don't show segwit if it's selected but network doesn't support it + if (segwitSelected() && !networkHasSegwit()) { + showSegwitUnavailable(); + hidePending(); return; } + showSegwitAvailable(); // Get the derivation path var derivationPath = getDerivationPath(); var errorText = findDerivationPathErrors(derivationPath); @@ -344,9 +386,12 @@ if (bip44TabSelected()) { displayBip44Info(); } - if (bip49TabSelected()) { + else if (bip49TabSelected()) { displayBip49Info(); } + else if (bip84TabSelected()) { + displayBip84Info(); + } displayBip32Info(); } @@ -380,8 +425,7 @@ }, 50); } - function useBitpayAddressesChange() { - setBitcoinCashNetworkValues(); + function bitcoinCashAddressTypeChange() { phraseChanged(); } @@ -405,6 +449,17 @@ $("td.privkey span").toggleClass("invisible"); } + function privacyScreenToggled() { + // private-data contains elements added to DOM at runtime + // so catch all by adding visual privacy class to the root of the DOM + if (DOM.privacyScreenToggle.prop("checked")) { + $("body").addClass("visual-privacy"); + } + else { + $("body").removeClass("visual-privacy"); + } + } + // Private methods function generateRandomPhrase() { @@ -413,10 +468,20 @@ showValidationError(errorText); return; } + // get the amount of entropy to use var numWords = parseInt(DOM.generatedStrength.val()); var strength = numWords / 3 * 32; - var words = mnemonic.generate(strength); + var buffer = new Uint8Array(strength / 8); + // create secure entropy + var data = crypto.getRandomValues(buffer); + // show the words + var words = mnemonic.toMnemonic(data); DOM.phrase.val(words); + // show the entropy + var entropyHex = uint8ArrayToHex(data); + DOM.entropy.val(entropyHex); + // ensure entropy fields are consistent with what is being displayed + DOM.entropyMnemonicLength.val("raw"); return words; } @@ -426,6 +491,37 @@ } function calcBip32RootKeyFromBase58(rootKeyBase58) { + // try parsing with various segwit network params since this extended + // key may be from any one of them. + if (networkHasSegwit()) { + var n = network; + if ("baseNetwork" in n) { + n = bitcoinjs.bitcoin.networks[n.baseNetwork]; + } + // try parsing using base network params + try { + bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n); + return; + } + catch (e) {} + // try parsing using p2wpkh params + if ("p2wpkh" in n) { + try { + bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); + return; + } + catch (e) {} + } + // try parsing using p2wpkh-in-p2sh network params + if ("p2wpkhInP2sh" in n) { + try { + bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + return; + } + catch (e) {} + } + } + // try the network params as currently specified bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); } @@ -499,6 +595,37 @@ } function validateRootKey(rootKeyBase58) { + // try various segwit network params since this extended key may be from + // any one of them. + if (networkHasSegwit()) { + var n = network; + if ("baseNetwork" in n) { + n = bitcoinjs.bitcoin.networks[n.baseNetwork]; + } + // try parsing using base network params + try { + bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n); + return ""; + } + catch (e) {} + // try parsing using p2wpkh params + if ("p2wpkh" in n) { + try { + bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); + return ""; + } + catch (e) {} + } + // try parsing using p2wpkh-in-p2sh network params + if ("p2wpkhInP2sh" in n) { + try { + bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + return ""; + } + catch (e) {} + } + } + // try the network params as currently specified try { bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); } @@ -524,7 +651,7 @@ console.log("Using derivation path from BIP44 tab: " + derivationPath); return derivationPath; } - if (bip49TabSelected()) { + else if (bip49TabSelected()) { var purpose = parseIntNoNaN(DOM.bip49purpose.val(), 49); var coin = parseIntNoNaN(DOM.bip49coin.val(), 0); var account = parseIntNoNaN(DOM.bip49account.val(), 0); @@ -539,11 +666,31 @@ console.log("Using derivation path from BIP49 tab: " + derivationPath); return derivationPath; } + else if (bip84TabSelected()) { + var purpose = parseIntNoNaN(DOM.bip84purpose.val(), 84); + var coin = parseIntNoNaN(DOM.bip84coin.val(), 0); + var account = parseIntNoNaN(DOM.bip84account.val(), 0); + var change = parseIntNoNaN(DOM.bip84change.val(), 0); + var path = "m/"; + path += purpose + "'/"; + path += coin + "'/"; + path += account + "'/"; + path += change; + DOM.bip84path.val(path); + var derivationPath = DOM.bip84path.val(); + console.log("Using derivation path from BIP84 tab: " + derivationPath); + return derivationPath; + } else if (bip32TabSelected()) { var derivationPath = DOM.bip32path.val(); console.log("Using derivation path from BIP32 tab: " + derivationPath); return derivationPath; } + else if (bip141TabSelected()) { + var derivationPath = DOM.bip141path.val(); + console.log("Using derivation path from BIP141 tab: " + derivationPath); + return derivationPath; + } else { console.log("Unknown derivation path"); } @@ -634,6 +781,24 @@ DOM.bip49accountXpub.val(accountXpub); } + function displayBip84Info() { + // Get the derivation path for the account + var purpose = parseIntNoNaN(DOM.bip84purpose.val(), 84); + var coin = parseIntNoNaN(DOM.bip84coin.val(), 0); + var account = parseIntNoNaN(DOM.bip84account.val(), 0); + var path = "m/"; + path += purpose + "'/"; + path += coin + "'/"; + path += account + "'/"; + // Calculate the account extended keys + var accountExtendedKey = calcBip32ExtendedKey(path); + var accountXprv = accountExtendedKey.toBase58(); + var accountXpub = accountExtendedKey.neutered().toBase58(); + // Display the extended keys + DOM.bip84accountXprv.val(accountXprv); + DOM.bip84accountXpub.val(accountXpub); + } + function displayBip32Info() { // Display the key DOM.seed.val(seed); @@ -649,7 +814,8 @@ DOM.extendedPubKey.val(extendedPubKey); // Display the addresses and privkeys clearAddressesList(); - displayAddresses(0, 20); + var initialAddressCount = parseInt(DOM.rowsToAdd.val()); + displayAddresses(0, initialAddressCount); } function displayAddresses(start, total) { @@ -673,8 +839,18 @@ })()); } - function P2wpkhNestedInP2shSelected() { - return bip49TabSelected() || (bip32TabSelected() && useP2wpkhNestedInP2sh()); + function segwitSelected() { + return bip49TabSelected() || bip84TabSelected() || bip141TabSelected(); + } + + function p2wpkhSelected() { + return bip84TabSelected() || + bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh"; + } + + function p2wpkhInP2shSelected() { + return bip49TabSelected() || + (bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh-p2sh"); } function TableRow(index, isLast) { @@ -682,8 +858,12 @@ var self = this; this.shouldGenerate = true; var useHardenedAddresses = DOM.hardenedAddresses.prop("checked"); - var isP2wpkhNestedInP2sh = P2wpkhNestedInP2shSelected(); - var p2wpkhNestedInP2shAvailable = networkHasBip49(); + var useBip38 = DOM.useBip38.prop("checked"); + var bip38password = DOM.bip38Password.val(); + var isSegwit = segwitSelected(); + var segwitAvailable = networkHasSegwit(); + var isP2wpkh = p2wpkhSelected(); + var isP2wpkhInP2sh = p2wpkhInP2shSelected(); function init() { calculateValues(); @@ -694,6 +874,7 @@ if (!self.shouldGenerate) { return; } + // derive HDkey for this row of the table var key = "NA"; if (useHardenedAddresses) { key = bip32ExtendedKey.deriveHardened(index); @@ -701,19 +882,47 @@ else { key = bip32ExtendedKey.derive(index); } - var address = key.getAddress().toString(); + // bip38 requires uncompressed keys + // see https://github.com/iancoleman/bip39/issues/140#issuecomment-352164035 + var keyPair = key.keyPair; + var useUncompressed = useBip38; + if (useUncompressed) { + keyPair = new bitcoinjs.bitcoin.ECPair(keyPair.d, null, { network: network, compressed: false }); + } + // get address + var address = keyPair.getAddress().toString(); + // get privkey + var hasPrivkey = !key.isNeutered(); var privkey = "NA"; - if (!key.isNeutered()) { - privkey = key.keyPair.toWIF(network); + if (hasPrivkey) { + privkey = keyPair.toWIF(); + // BIP38 encode private key if required + if (useBip38) { + privkey = bitcoinjsBip38.encrypt(keyPair.d.toBuffer(), false, bip38password, function(p) { + console.log("Progressed " + p.percent.toFixed(1) + "% for index " + index); + }); + } } - var pubkey = key.getPublicKeyBuffer().toString('hex'); + // get pubkey + var pubkey = keyPair.getPublicKeyBuffer().toString('hex'); var indexText = getDerivationPath() + "/" + index; if (useHardenedAddresses) { indexText = indexText + "'"; } // Ethereum values are different - if (networks[DOM.network.val()].name == "ETH - Ethereum") { - var privKeyBuffer = key.keyPair.d.toBuffer(); + if ((networks[DOM.network.val()].name == "ETH - Ethereum") + || (networks[DOM.network.val()].name == "ETC - Ethereum Classic") + || (networks[DOM.network.val()].name == "PIRL - Pirl") + || (networks[DOM.network.val()].name == "MIX - MIX") + || (networks[DOM.network.val()].name == "MUSIC - Musicoin") + || (networks[DOM.network.val()].name == "POA - Poa") + || (networks[DOM.network.val()].name == "EXP - Expanse") + || (networks[DOM.network.val()].name == "CLO - Callisto") + || (networks[DOM.network.val()].name == "DXN - DEXON") + || (networks[DOM.network.val()].name == "ELLA - Ellaism") + || (networks[DOM.network.val()].name == "ESN - Ethersocial Network") + ) { + var privKeyBuffer = keyPair.d.toBuffer(32); privkey = privKeyBuffer.toString('hex'); var addressBuffer = ethUtil.privateToAddress(privKeyBuffer); var hexAddress = addressBuffer.toString('hex'); @@ -722,25 +931,83 @@ privkey = ethUtil.addHexPrefix(privkey); pubkey = ethUtil.addHexPrefix(pubkey); } + + // Stellar is different + if (networks[DOM.network.val()].name == "XLM - Stellar") { + var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44); + var coin = parseIntNoNaN(DOM.bip44coin.val(), 0); + var path = "m/"; + path += purpose + "'/"; + path += coin + "'/" + index + "'"; + var keypair = stellarUtil.getKeypair(path, seed); + indexText = path; + privkey = keypair.secret(); + pubkey = address = keypair.publicKey(); + } + if ((networks[DOM.network.val()].name == "NAS - Nebulas")) { + var NasAccount = require("nebulas-account"); + var privKeyBuffer = keyPair.d.toBuffer(32); + var nebulasAccount = new NasAccount(); + nebulasAccount.setPrivateKey(privKeyBuffer); + address = nebulasAccount.getAddressString(); + privkey = nebulasAccount.getPrivateKeyString(); + pubkey = nebulasAccount.getPublicKeyString(); + } // Ripple values are different if (networks[DOM.network.val()].name == "XRP - Ripple") { privkey = convertRipplePriv(privkey); address = convertRippleAdrr(address); } - // BIP49 addresses are different - if (isP2wpkhNestedInP2sh) { - if (!p2wpkhNestedInP2shAvailable) { + // Bitcoin Cash address format may vary + if (networks[DOM.network.val()].name == "BCH - Bitcoin Cash") { + var bchAddrType = DOM.bitcoinCashAddressType.filter(":checked").val(); + if (bchAddrType == "cashaddr") { + address = bchaddr.toCashAddress(address); + } + else if (bchAddrType == "bitpay") { + address = bchaddr.toBitpayAddress(address); + } + } + // Bitcoin Cash address format may vary + if (networks[DOM.network.val()].name == "SLP - Simple Ledger Protocol") { + var bchAddrType = DOM.bitcoinCashAddressType.filter(":checked").val(); + if (bchAddrType == "cashaddr") { + address = bchaddr.toSlpAddress(address); + } + } + // Segwit addresses are different + if (isSegwit) { + if (!segwitAvailable) { return; } - var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); - var scriptsig = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); - var addressbytes = bitcoinjs.bitcoin.crypto.hash160(scriptsig); - var scriptpubkey = bitcoinjs.bitcoin.script.scriptHash.output.encode(addressbytes); - address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network) + if (isP2wpkh) { + var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); + var scriptpubkey = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); + address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network) + } + else if (isP2wpkhInP2sh) { + var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); + var scriptsig = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); + var addressbytes = bitcoinjs.bitcoin.crypto.hash160(scriptsig); + var scriptpubkey = bitcoinjs.bitcoin.script.scriptHash.output.encode(addressbytes); + address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network) + } + } + + if ((networks[DOM.network.val()].name == "CRW - Crown")) { + address = bitcoinjs.bitcoin.networks.crown.toNewAddress(address); + } + + if (networks[DOM.network.val()].name == "EOS - EOSIO") { + address = "" + pubkey = eosUtil.bufferToPublic(keyPair.getPublicKeyBuffer()); + privkey = eosUtil.bufferToPrivate(keyPair.d.toBuffer(32)); } + addAddressToList(indexText, address, pubkey, privkey); if (isLast) { hidePending(); + updateCsv(); } }, 50) } @@ -750,12 +1017,19 @@ } function showMore() { - var start = DOM.addresses.children().length; var rowsToAdd = parseInt(DOM.rowsToAdd.val()); if (isNaN(rowsToAdd)) { rowsToAdd = 20; DOM.rowsToAdd.val("20"); } + var start = parseInt(DOM.moreRowsStartIndex.val()) + if (isNaN(start)) { + start = lastIndexInTable() + 1; + } + else { + var newStart = start + rowsToAdd; + DOM.moreRowsStartIndex.val(newStart); + } if (rowsToAdd > 200) { var msg = "Generating " + rowsToAdd + " rows could take a while. "; msg += "Do you want to continue?"; @@ -774,6 +1048,7 @@ function clearAddressesList() { DOM.addresses.empty(); + DOM.csv.val(""); stopGenerating(); } @@ -1050,6 +1325,17 @@ mnemonicLength = parseInt(mnemonicLength); var numberOfBits = 32 * mnemonicLength / 3; bits = bits.substring(0, numberOfBits); + // show warning for weak entropy override + if (mnemonicLength / 3 * 32 > entropy.binaryStr.length) { + DOM.entropyWeakEntropyOverrideWarning.removeClass("hidden"); + } + else { + DOM.entropyWeakEntropyOverrideWarning.addClass("hidden"); + } + } + else { + // hide warning for weak entropy override + DOM.entropyWeakEntropyOverrideWarning.addClass("hidden"); } // Discard trailing entropy var bitsToUse = Math.floor(bits.length / 32) * 32; @@ -1066,6 +1352,10 @@ var phrase = mnemonic.toMnemonic(entropyArr); // Set the mnemonic in the UI DOM.phrase.val(phrase); + // Show the word indexes + showWordIndexes(); + // Show the checksum + showChecksum(); } function clearEntropyFeedback() { @@ -1096,13 +1386,14 @@ var entropyTypeStr = getEntropyTypeStr(entropy); var wordCount = Math.floor(numberOfBits / 32) * 3; var bitsPerEvent = entropy.bitsPerEvent.toFixed(2); + var spacedBinaryStr = addSpacesEveryElevenBits(entropy.binaryStr); DOM.entropyFiltered.html(entropy.cleanHtml); DOM.entropyType.text(entropyTypeStr); DOM.entropyCrackTime.text(timeToCrack); DOM.entropyEventCount.text(entropy.base.ints.length); DOM.entropyBits.text(numberOfBits); DOM.entropyWordCount.text(wordCount); - DOM.entropyBinary.text(entropy.binaryStr); + DOM.entropyBinary.text(spacedBinaryStr); DOM.entropyBitsPerEvent.text(bitsPerEvent); // detect and warn of filtering var rawNoSpaces = DOM.entropy.val().replace(/\s/g, ""); @@ -1188,8 +1479,13 @@ function createQr(e) { var content = e.target.textContent || e.target.value; if (content) { - var size = 130; - DOM.qrImage.qrcode({width: size, height: size, text: content}); + var qrEl = kjua({ + text: content, + render: "canvas", + size: 310, + ecLevel: 'H', + }); + DOM.qrImage.append(qrEl); if (!showQr) { DOM.qrHider.addClass("hidden"); } @@ -1219,327 +1515,1351 @@ return DOM.bip32tab.hasClass("active"); } - function useP2wpkhNestedInP2sh() { - return DOM.useP2wpkhNestedInP2sh.prop("checked"); - } - - function networkHasBip49() { - return networks[DOM.network.val()].p2wpkhNestedInP2shAvailable; + function networkHasSegwit() { + var n = network; + if ("baseNetwork" in network) { + n = bitcoinjs.bitcoin.networks[network.baseNetwork]; + } + // check if only p2wpkh params are required + if (p2wpkhSelected()) { + return "p2wpkh" in n; + } + // check if only p2wpkh-in-p2sh params are required + else if (p2wpkhInP2shSelected()) { + return "p2wpkhInP2sh" in n; + } + // require both if it's unclear which params are required + return "p2wpkh" in n && "p2wpkhInP2sh" in n; } function bip49TabSelected() { return DOM.bip49tab.hasClass("active"); } + function bip84TabSelected() { + return DOM.bip84tab.hasClass("active"); + } + + function bip141TabSelected() { + return DOM.bip141tab.hasClass("active"); + } + function setHdCoin(coinValue) { DOM.bip44coin.val(coinValue); DOM.bip49coin.val(coinValue); + DOM.bip84coin.val(coinValue); } - function showP2wpkhNestedInP2shAvailable() { + function showSegwitAvailable() { DOM.bip49unavailable.addClass("hidden"); DOM.bip49available.removeClass("hidden"); - DOM.useP2wpkhNestedInP2sh.prop("disabled", false); + DOM.bip84unavailable.addClass("hidden"); + DOM.bip84available.removeClass("hidden"); + DOM.bip141unavailable.addClass("hidden"); + DOM.bip141available.removeClass("hidden"); } - function showP2wpkhNestedInP2shUnavailable() { + function showSegwitUnavailable() { DOM.bip49available.addClass("hidden"); DOM.bip49unavailable.removeClass("hidden"); - DOM.useP2wpkhNestedInP2sh.prop("disabled", true); - DOM.useP2wpkhNestedInP2sh.prop("checked", false); + DOM.bip84available.addClass("hidden"); + DOM.bip84unavailable.removeClass("hidden"); + DOM.bip141available.addClass("hidden"); + DOM.bip141unavailable.removeClass("hidden"); + } + + function adjustNetworkForSegwit() { + // If segwit is selected the xpub/xprv prefixes need to be adjusted + // to avoid accidentally importing BIP49 xpub to BIP44 watch only + // wallet. + // See https://github.com/iancoleman/bip39/issues/125 + var segwitNetworks = null; + // if a segwit network is alread selected, need to use base network to + // look up new parameters + if ("baseNetwork" in network) { + network = bitcoinjs.bitcoin.networks[network.baseNetwork]; + } + // choose the right segwit params + if (p2wpkhSelected() && "p2wpkh" in network) { + network = network.p2wpkh; + } + else if (p2wpkhInP2shSelected() && "p2wpkhInP2sh" in network) { + network = network.p2wpkhInP2sh; + } } - function useBitpayAddresses() { - return !(DOM.useBitpayAddresses.prop("checked")); + function lastIndexInTable() { + var pathText = DOM.addresses.find(".index").last().text(); + var pathBits = pathText.split("/"); + var lastBit = pathBits[pathBits.length-1]; + var lastBitClean = lastBit.replace("'", ""); + return parseInt(lastBitClean); } - function setBitcoinCashNetworkValues() { - if (useBitpayAddresses()) { - network = bitcoinjs.bitcoin.networks.bitcoin; + function uint8ArrayToHex(a) { + var s = "" + for (var i=0; i=0; i--) { + var word = words[i]; + var wordIndex = WORDLISTS[language].indexOf(word); + var wordBinary = wordIndex.toString(2); + while (wordBinary.length < 11) { + wordBinary = "0" + wordBinary; } - else if (network == bitcoinjs.bitcoin.networks.litecoin) { - network = bitcoinjs.bitcoin.networks.litecoinBip49; + var binaryStr = wordBinary + binaryStr; + if (binaryStr.length >= checksumBitlength) { + var start = binaryStr.length - checksumBitlength; + var end = binaryStr.length; + checksum = binaryStr.substring(start, end); + // add spaces so the last group is 11 bits, not the first + checksum = checksum.split("").reverse().join("") + checksum = addSpacesEveryElevenBits(checksum); + checksum = checksum.split("").reverse().join("") + break; } } - else { - if (network == bitcoinjs.bitcoin.networks.bitcoinBip49) { - network = bitcoinjs.bitcoin.networks.bitcoin; - } - else if (network == bitcoinjs.bitcoin.networks.testnetBip49) { - network = bitcoinjs.bitcoin.networks.testnet; - } - else if (network == bitcoinjs.bitcoin.networks.litecoinBip49) { - network = bitcoinjs.bitcoin.networks.litecoin; + DOM.entropyChecksum.text(checksum); + } + + function updateCsv() { + var tableCsv = "path,address,public key,private key\n"; + var rows = DOM.addresses.find("tr"); + for (var i=0; i