X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=src%2Fjs%2Findex.js;h=f13063cdc5b0ee6cec1190776194b2a03e4d5e6e;hb=d00c719932123feb4135b4ef537b28e8eb735cd5;hp=45c7cb1f26d69de969735171d234ec82057653b7;hpb=0cda44d5f6ca63520ad39e88771c374da2993d6c;p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FBIP39.git diff --git a/src/js/index.js b/src/js/index.js index 45c7cb1..f13063c 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; @@ -37,7 +37,9 @@ 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.entropyMnemonicLength = DOM.entropyContainer.find(".mnemonic-length"); + DOM.entropyFilterWarning = DOM.entropyContainer.find(".filter-warning"); DOM.phrase = $(".phrase"); DOM.passphrase = $(".passphrase"); DOM.generateContainer = $(".generate-container"); @@ -51,6 +53,7 @@ DOM.bip32tab = $("#bip32-tab"); DOM.bip44tab = $("#bip44-tab"); DOM.bip49tab = $("#bip49-tab"); + DOM.bip141tab = $("#bip141-tab"); DOM.bip32panel = $("#bip32"); DOM.bip44panel = $("#bip44"); DOM.bip49panel = $("#bip49"); @@ -71,11 +74,18 @@ DOM.bip49accountXprv = $("#bip49 .account-xprv"); DOM.bip49accountXpub = $("#bip49 .account-xpub"); DOM.bip49change = $("#bip49 .change"); + DOM.bip141unavailable = $("#bip141 .unavailable"); + DOM.bip141available = $("#bip141 .available"); + DOM.bip141path = $("#bip141-path"); + DOM.bip141semantics = $(".bip141-semantics"); DOM.generatedStrength = $(".generate-container .strength"); DOM.hardenedAddresses = $(".hardened-addresses"); + DOM.useBitpayAddressesContainer = $(".use-bitpay-addresses-container"); + DOM.useBitpayAddresses = $(".use-bitpay-addresses"); DOM.addresses = $(".addresses"); 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"); @@ -107,13 +117,16 @@ DOM.bip44change.on("input", calcForDerivationPath); DOM.bip49account.on("input", calcForDerivationPath); DOM.bip49change.on("input", calcForDerivationPath); - DOM.tab.on("shown.bs.tab", calcForDerivationPath); + DOM.bip141path.on("input", calcForDerivationPath); + DOM.bip141semantics.on("change", tabChanged); + DOM.tab.on("shown.bs.tab", tabChanged); DOM.hardenedAddresses.on("change", calcForDerivationPath); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.publicKeyToggle.on("click", togglePublicKeys); DOM.privateKeyToggle.on("click", togglePrivateKeys); DOM.languages.on("click", languageChanged); + DOM.useBitpayAddresses.on("change", useBitpayAddressesChange); setQrEvents(DOM.showQrEls); disableForms(); hidePending(); @@ -128,14 +141,16 @@ clearDerivedKeys(); clearAddressesList(); DOM.litecoinLtubContainer.addClass("hidden"); + DOM.useBitpayAddressesContainer.addClass("hidden"); var networkIndex = e.target.value; var network = networks[networkIndex]; network.onSelect(); - if (network.p2wpkhNestedInP2shAvailable) { - showP2wpkhNestedInP2shAvailable(); + if (network.segwitAvailable) { + adjustNetworkForSegwit(); + showSegwitAvailable(); } else { - showP2wpkhNestedInP2shUnavailable(); + showSegwitUnavailable(); } if (seed != null) { phraseChanged(); @@ -205,6 +220,37 @@ var passphrase = DOM.passphrase.val(); calcBip32RootKeyFromSeed(phrase, passphrase); calcForDerivationPath(); + // Show the word indexes + showWordIndexes(); + } + + function tabChanged() { + showPending(); + adjustNetworkForSegwit(); + var phrase = DOM.phrase.val(); + if (phrase != "") { + // Calculate and display for mnemonic + var errorText = findPhraseErrors(phrase); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + var passphrase = DOM.passphrase.val(); + calcBip32RootKeyFromSeed(phrase, passphrase); + } + else { + // Calculate and display for root key + var rootKeyBase58 = DOM.rootKey.val(); + var errorText = validateRootKey(rootKeyBase58); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + calcBip32RootKeyFromBase58(rootKeyBase58); + } + calcForDerivationPath(); } function delayedEntropyChanged() { @@ -267,7 +313,6 @@ function rootKeyChanged() { showPending(); hideValidationError(); - // Validate the root key TODO var rootKeyBase58 = DOM.rootKey.val(); var errorText = validateRootKey(rootKeyBase58); if (errorText) { @@ -282,10 +327,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(); } @@ -294,8 +339,8 @@ 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()) { return; } // Get the derivation path @@ -309,7 +354,7 @@ if (bip44TabSelected()) { displayBip44Info(); } - if (bip49TabSelected()) { + else if (bip49TabSelected()) { displayBip49Info(); } displayBip32Info(); @@ -345,6 +390,11 @@ }, 50); } + function useBitpayAddressesChange() { + setBitcoinCashNetworkValues(); + phraseChanged(); + } + function toggleIndexes() { showIndex = !showIndex; $("td.index span").toggleClass("invisible"); @@ -373,10 +423,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; } @@ -460,7 +520,7 @@ function validateRootKey(rootKeyBase58) { try { - bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58); + bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); } catch (e) { return "Invalid root key"; @@ -484,7 +544,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); @@ -504,6 +564,11 @@ 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"); } @@ -633,13 +698,28 @@ })()); } + function segwitSelected() { + return bip49TabSelected() || bip141TabSelected(); + } + + function p2wpkhSelected() { + return bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh"; + } + + function p2wpkhInP2shSelected() { + return bip49TabSelected() || + (bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh-p2sh"); + } + function TableRow(index, isLast) { var self = this; this.shouldGenerate = true; var useHardenedAddresses = DOM.hardenedAddresses.prop("checked"); - var isP2wpkhNestedInP2sh = bip49TabSelected(); - var p2wpkhNestedInP2shAvailable = networkHasBip49(); + var isSegwit = segwitSelected(); + var segwitAvailable = networkHasSegwit(); + var isP2wpkh = p2wpkhSelected(); + var isP2wpkhInP2sh = p2wpkhInP2shSelected(); function init() { calculateValues(); @@ -683,17 +763,25 @@ privkey = convertRipplePriv(privkey); address = convertRippleAdrr(address); } - // BIP49 addresses are different - if (isP2wpkhNestedInP2sh) { - if (!p2wpkhNestedInP2shAvailable) { + // 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) + } } + console.log(address); addAddressToList(indexText, address, pubkey, privkey); if (isLast) { hidePending(); @@ -706,12 +794,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?"; @@ -1022,6 +1117,8 @@ var phrase = mnemonic.toMnemonic(entropyArr); // Set the mnemonic in the UI DOM.phrase.val(phrase); + // Show the word indexes + showWordIndexes(); } function clearEntropyFeedback() { @@ -1060,6 +1157,16 @@ DOM.entropyWordCount.text(wordCount); DOM.entropyBinary.text(entropy.binaryStr); DOM.entropyBitsPerEvent.text(bitsPerEvent); + // detect and warn of filtering + var rawNoSpaces = DOM.entropy.val().replace(/\s/g, ""); + var cleanNoSpaces = entropy.cleanStr.replace(/\s/g, ""); + var isFiltered = rawNoSpaces.length != cleanNoSpaces.length; + if (isFiltered) { + DOM.entropyFilterWarning.removeClass('hidden'); + } + else { + DOM.entropyFilterWarning.addClass('hidden'); + } } function getEntropyTypeStr(entropy) { @@ -1134,8 +1241,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"); } @@ -1165,41 +1277,117 @@ return DOM.bip32tab.hasClass("active"); } - function networkHasBip49() { - return networks[DOM.network.val()].p2wpkhNestedInP2shAvailable; + function networkHasSegwit() { + return networks[DOM.network.val()].segwitAvailable; } function bip49TabSelected() { return DOM.bip49tab.hasClass("active"); } + function bip141TabSelected() { + return DOM.bip141tab.hasClass("active"); + } + function setHdCoin(coinValue) { DOM.bip44coin.val(coinValue); DOM.bip49coin.val(coinValue); } - function showP2wpkhNestedInP2shAvailable() { + function showSegwitAvailable() { DOM.bip49unavailable.addClass("hidden"); DOM.bip49available.removeClass("hidden"); + DOM.bip141unavailable.addClass("hidden"); + DOM.bip141available.removeClass("hidden"); } - function showP2wpkhNestedInP2shUnavailable() { + function showSegwitUnavailable() { DOM.bip49available.addClass("hidden"); DOM.bip49unavailable.removeClass("hidden"); + DOM.bip141available.addClass("hidden"); + DOM.bip141unavailable.removeClass("hidden"); + } + + function useBitpayAddresses() { + return !(DOM.useBitpayAddresses.prop("checked")); + } + + function setBitcoinCashNetworkValues() { + if (useBitpayAddresses()) { + network = bitcoinjs.bitcoin.networks.bitcoin; + } + else { + network = bitcoinjs.bitcoin.networks.bitcoinCashBitbpay; + } + } + + 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 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 uint8ArrayToHex(a) { + var s = "" + for (var i=0; i