(function($){ var bip32_source_key = null; var bip32_derivation_path = null; var gen_from = "pass"; var hash_worker = null; var hash_worker_working = false; var bip32_passphrase_hash = null; var TIMEOUT = 600; var timeout = null; var coin = "btc_main"; var COINS = { btc_main: { name: "Bitcoin", network: "Mainnet", prefix: 0, private_prefix: 0+0x80, bip32_public: BITCOIN_MAINNET_PUBLIC, bip32_private: BITCOIN_MAINNET_PRIVATE }, btc_test: { name: "Bitcoin", network: "Testnet", prefix: 0x6f, private_prefix: 0x6f+0x80, bip32_public: BITCOIN_TESTNET_PUBLIC, bip32_private: BITCOIN_TESTNET_PRIVATE }, bch_main: { name: "Bitcoin Cash", network: "Mainnet", prefix: 0, private_prefix: 0+0x80, bip32_public: BITCOIN_CASH_MAINNET_PUBLIC, bip32_private: BITCOIN_CASH_MAINNET_PRIVATE }, bch_test: { name: "Bitcoin Cash", network: "Testnet", prefix: 0x6f, private_prefix: 0x6f+0x80, bip32_public: BITCOIN_CASH_TESTNET_PUBLIC, bip32_private: BITCOIN_CASH_TESTNET_PRIVATE }, doge_main: { name: "Dogecoin", network: "Mainnet", prefix: 0x1e, private_prefix: 0x1e+0x80, bip32_public: DOGECOIN_MAINNET_PUBLIC, bip32_private: DOGECOIN_MAINNET_PRIVATE }, doge_test: { name: "Dogecoin", network: "Testnet", prefix: 0x71, private_prefix: 0x71+0x80, bip32_public: DOGECOIN_TESTNET_PUBLIC, bip32_private: DOGECOIN_TESTNET_PRIVATE }, jbs_main: { name: "Jumbucks", network: "Mainnet", prefix: 0x2b, private_prefix: 0x2b+0x80, bip32_public: JUMBUCKS_MAINNET_PUBLIC, bip32_private: JUMBUCKS_MAINNET_PRIVATE }, ltc_main: { name: "Litecoin", network: "Mainnet", prefix: 0x30, private_prefix: 0x30+0x80, bip32_public: LITECOIN_MAINNET_PUBLIC, bip32_private: LITECOIN_MAINNET_PRIVATE }, ltc_test: { name: "Litecoin", network: "Testnet", prefix: 0x6f, private_prefix: 0x6f+0x80, bip32_public: LITECOIN_TESTNET_PUBLIC, bip32_private: LITECOIN_TESTNET_PRIVATE } }; var ADDRESS_URL_PREFIX = 'http://blockchain.info/address/' var acknowledged_bch_is_experimental = false; function pad(str, len, ch) { padding = ''; for (var i = 0; i < len - str.length; i++) { padding += ch; } return padding + str; } function setWarningState(field, err, msg) { var group = field.closest('.form-group'); if (err) { group.removeClass('has-error').addClass('has-warning'); group.attr('title', msg); } else { group.removeClass('has-warning').removeClass('has-error'); group.attr('title', ''); } } function setErrorState(field, err, msg) { var group = field.closest('.form-group'); if (err) { group.removeClass('has-warning').addClass('has-error'); group.attr('title',msg); } else { group.removeClass('has-warning').removeClass('has-error'); group.attr('title',''); } } function pad2(s) { if(s.length == 1) return '0' + s; return s; } function pad8(s) { while(s.length < 8) s = '0' + s; return s; } function byteArrayToHexString(a) { var s = ''; for( var i in a ) { s = s + pad2(a[i].toString(16)); } return s; } // --- bip32 --- function onUpdateGenFrom() { gen_from = $(this).attr('id').substring(5); updateGenFrom(); } function updateGenFrom() { if( gen_from == 'pass' ) { $("#bip32_source_passphrase").attr('readonly', false); $("#bip32_source_key").attr('readonly', true); $("#gen_from_msg").html("Your passphrase is hashed using 50,000 rounds of HMAC-SHA256"); } else { setErrorState($("#bip32_source_passphrase"), false); $("#bip32_source_passphrase").attr('readonly', true); $("#bip32_source_key").attr('readonly', false); stop_hash_worker(); $("#cancel_hash_worker").attr('disabled', true); $("#gen_from_msg").html("You can manually enter an Extended Private or Public key"); } } function onUpdateSourcePassphrase() { clearTimeout(timeout); timeout = setTimeout(updateSourcePassphrase, TIMEOUT); setWarningState($("#bip32_source_passphrase"), false); } function onShowPassphraseChanged() { if($(this).is(":checked")) { $("#bip32_source_passphrase").attr('type', 'text'); } else { $("#bip32_source_passphrase").attr('type', 'password'); } } function onCancelHashWorkerClicked() { stop_hash_worker(); var passphrase = $("#bip32_source_passphrase").val(); bip32_passphrase_hash = Crypto.util.bytesToHex(Crypto.SHA256(passphrase, { asBytes: true })); updatePassphraseHash(); setWarningState($("#bip32_source_passphrase"), true, "The passphrase was hashed using a single SHA-256 and should be considered WEAK and INSECURE"); } function updateSourcePassphrase() { var passphrase = $("#bip32_source_passphrase").val(); if( typeof(Worker) === undefined ) { setErrorState($("#bip32_source_passphrase"), true, "Your browser doesn't support Web Workers"); } else { setErrorState($("#bip32_source_passphrase"), false); } try { start_hash_worker(passphrase); } catch (err) { setErrorState($("#bip32_source_passphrase"), true, "Your browser doesn't support Web Workers: " + err.toString()); alert("It appears your browser cannot load or execute web workers. If you are running locally using Chrome, run with the --allow-file-access-from-files option or use a HTTP server such as Python (python3 -m http.server)"); } } function updatePassphraseHash() { var hasher = new jsSHA(bip32_passphrase_hash, 'HEX'); var I = hasher.getHMAC("Bitcoin seed", "TEXT", "SHA-512", "HEX"); var il = Crypto.util.hexToBytes(I.slice(0, 64)); var ir = Crypto.util.hexToBytes(I.slice(64, 128)); var gen_bip32 = new BIP32(); try { gen_bip32.eckey = new Bitcoin.ECKey(il); gen_bip32.eckey.pub = gen_bip32.eckey.getPubPoint(); gen_bip32.eckey.setCompressed(true); gen_bip32.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(gen_bip32.eckey.pub.getEncoded(true)); gen_bip32.has_private_key = true; gen_bip32.chain_code = ir; gen_bip32.child_index = 0; gen_bip32.parent_fingerprint = Bitcoin.Util.hexToBytes("00000000"); gen_bip32.version = COINS[coin].bip32_private; gen_bip32.depth = 0; gen_bip32.build_extended_public_key(); gen_bip32.build_extended_private_key(); } catch (err) { setErrorState($('#bip32_source_passphrase'), true, '' + err); return; } setErrorState($('#bip32_source_passphrase'), false); $("#bip32_source_key").val(gen_bip32.extended_private_key_string("base58")); updateSourceKey(); } function isMasterKey(k) { return k.child_index == 0 && k.depth == 0 && ( k.parent_fingerprint[0] == 0 && k.parent_fingerprint[1] == 0 && k.parent_fingerprint[2] == 0 && k.parent_fingerprint[3] == 0 ); } function onUpdateSourceKey() { clearTimeout(timeout); timeout = setTimeout(updateSourceKey, TIMEOUT); } function updateSourceKey() { $("#bip32_key_info_title").html(''); $("#bip32_key_info_version").val(''); $("#bip32_key_info_depth").val(''); $("#bip32_key_info_parent_fingerprint").val(''); $("#bip32_key_info_child_index").val(''); $("#bip32_key_info_chain_code").val(''); $("#bip32_key_info_key").val(''); setErrorState($('#bip32_source_key'), false); try { var source_key_str = $("#bip32_source_key").val(); if(source_key_str.length == 0) return; bip32_source_key = new BIP32(source_key_str); } catch(err) { bip32_source_key = null; setErrorState($('#bip32_source_key'), true, 'Invalid key: ' + err.toString()); return; } var coin = getCoinFromKey(bip32_source_key); if(coin.name == "Bitcoin Cash" && !acknowledged_bch_is_experimental) { alert("Warning!\n\nBitcoin Cash support is NEW and EXPERIMENTAL. Use at your own risk!\n\nPlease verify your addresses with other services before using!"); acknowledged_bch_is_experimental = true; } for(var coin_name in COINS) { if(coin.bip32_private == COINS[coin_name].bip32_private) { $('#crName').text($("#" + coin_name).text()); break; } } //console.log(bip32_source_key); updateSourceKeyInfo(); updateDerivationPath(); } function getCoinFromKey(k) { for(var coin_name in COINS) { var c = COINS[coin_name]; if(k.version == c.bip32_public || k.version == c.bip32_private) { return c; } } return null; } function updateSourceKeyInfo() { var key_coin = getCoinFromKey(bip32_source_key); if( isMasterKey(bip32_source_key) ) { if( bip32_source_key.has_private_key ) { $("#bip32_key_info_title").html("" + key_coin.name + " Master Private Key"); } else { $("#bip32_key_info_title").html("" + key_coin.name + " Master Public Key"); } } else { if( bip32_source_key.has_private_key ) { $("#bip32_key_info_title").html("" + key_coin.name + " Derived Private Key"); } else { $("#bip32_key_info_title").html("" + key_coin.name + " Derived Public Key"); } } var v = '' + pad8(bip32_source_key.version.toString(16)); if( bip32_source_key.has_private_key ) v = v + " (" + key_coin.name + " " + key_coin.network + " private key)"; else v = v + " (" + key_coin.name + " " + key_coin.network + " public key)"; $("#bip32_key_info_version").val(v); $("#bip32_key_info_depth").val('' + bip32_source_key.depth); $("#bip32_key_info_parent_fingerprint").val('' + pad2(bip32_source_key.parent_fingerprint[0].toString(16)) + pad2(bip32_source_key.parent_fingerprint[1].toString(16)) + pad2(bip32_source_key.parent_fingerprint[2].toString(16)) + pad2(bip32_source_key.parent_fingerprint[3].toString(16))); $("#bip32_key_info_child_index").val(bip32_source_key.child_index); $("#bip32_key_info_chain_code").val('' + byteArrayToHexString(bip32_source_key.chain_code)); if( bip32_source_key.has_private_key ) { var bytes = [key_coin.private_prefix].concat(bip32_source_key.eckey.priv.toByteArrayUnsigned()).concat([1]); var checksum = Crypto.SHA256(Crypto.SHA256(bytes, {asBytes: true}), {asBytes: true}).slice(0, 4); $("#bip32_key_info_key").val(Bitcoin.Base58.encode(bytes.concat(checksum))); } else { var bytes = Crypto.util.bytesToHex(bip32_source_key.eckey.pub.getEncoded(true)); $("#bip32_key_info_key").val(bytes); } return; } function onUpdateDerivationPath() { updateDerivationPath(); } function onUpdateCustomPath() { clearTimeout(timeout); timeout = setTimeout(updateDerivationPath, TIMEOUT); } function onAccountIndexChanged() { clearTimeout(timeout); timeout = setTimeout(updateDerivationPath, TIMEOUT); } function onKeypairIndexChanged() { clearTimeout(timeout); timeout = setTimeout(updateDerivationPath, TIMEOUT); } function updateDerivationPath() { bip32_derivation_path = $("#bip32_derivation_path :selected").val(); if( bip32_derivation_path == "custom" ) { $("#custom_group").show(); bip32_derivation_path = $("#bip32_custom_path").val(); } else { $("#custom_group").hide(); } if( bip32_derivation_path.indexOf('/k/') >= 0 || bip32_derivation_path.indexOf('/k\'/') >= 0 ) { $("#account_group").show(); } else { $("#account_group").hide(); } if( bip32_derivation_path.indexOf('/i/') >= 0 || bip32_derivation_path.indexOf('/i\'/') >= 0 || bip32_derivation_path.slice(bip32_derivation_path.length-2) == "/i" || bip32_derivation_path.slice(bip32_derivation_path.length-3) == "/i'" ) { $("#child_group").show(); } else { $("#child_group").hide(); } updateResult(); } function updateResult() { var p = '' + bip32_derivation_path; var k = parseInt($("#account_index").val()); var i = parseInt($("#keypair_index").val()); p = p.replace('i', i).replace('k', k); setErrorState($('#bip32_derivation_path'), false); $("#derived_private_key").val(''); $("#derived_public_key").val(''); $("#derived_private_key_wif").val(''); $("#derived_public_key_hex").val(''); $("#addr").val(''); $("#cashaddr").val(''); $("#genKeyQR").html(''); $("#genKeyURL").hide(); $("#revealKeyQR").show(); $("#genAddrQR").html(''); $("#genCashAddrQR").html(''); try { if(bip32_source_key == null) { // if this is the case then there's an error state set on the source key return; } console.log("Deriving: " + p); var result = bip32_source_key.derive(p); } catch (err) { setErrorState($('#bip32_derivation_path'), true, 'Error deriving key: ' + err.toString()); return; } var key_coin = getCoinFromKey(result); if( result.has_private_key ) { $("#derived_private_key").val(result.extended_private_key_string("base58")); var privkeyBytes = result.eckey.priv.toByteArrayUnsigned(); while (privkeyBytes.length < 32) { privkeyBytes.unshift(0); }; var bytes = [key_coin.private_prefix].concat(privkeyBytes).concat([1]); var checksum = Crypto.SHA256(Crypto.SHA256(bytes, {asBytes: true}), {asBytes: true}).slice(0, 4); $("#derived_private_key_wif").val(Bitcoin.Base58.encode(bytes.concat(checksum))) } else { $("#derived_private_key").val("No private key available"); $("#derived_private_key_wif").val("No private key available"); } $("#derived_public_key").val(result.extended_public_key_string("base58")); $("#derived_public_key_hex").val(Crypto.util.bytesToHex(result.eckey.pub.getEncoded(true))); var hash160 = result.eckey.pubKeyHash; var addr = new Bitcoin.Address(hash160); addr.version = key_coin.prefix; $("#addr").val(addr.toString()); var qrCode = qrcode(3, 'M'); var text = $('#addr').val(); text = text.replace(/^[\s\u3000]+|[\s\u3000]+$/g, ''); qrCode.addData(text); qrCode.make(); $('#genAddrQR').html(qrCode.createImgTag(4)); $('#genAddrURL').attr('href', ADDRESS_URL_PREFIX+text); $('#genAddrURL').attr('title', text); if(key_coin.name == "Bitcoin Cash") { var bch_addr; if(key_coin.bip32_private == BITCOIN_CASH_TESTNET_PRIVATE) { bch_addr = cashaddr.encode("bchtest", "P2PKH", new Uint8Array(result.eckey.pubKeyHash)); } else { bch_addr = cashaddr.encode("bitcoincash", "P2PKH", new Uint8Array(result.eckey.pubKeyHash)); } $("#cashaddr").val(bch_addr); $("#addrlabel").text("Legacy Address"); $("#qraddrlabel").text("Legacy Address QR Code"); qrCode = qrcode(4, 'M'); qrCode.addData(bch_addr); qrCode.make(); $('#genCashAddrQR').html(qrCode.createImgTag(4)); $('#genCashAddrURL').attr('href', ADDRESS_URL_PREFIX+bch_addr); $('#genCashAddrURL').attr('title', bch_addr); $("#cashaddr_group").show(); } else { $("#addrlabel").text("Address"); $("#qraddrlabel").text("Address QR Code"); $("#cashaddr_group").hide(); } } function onInput(id, func) { $(id).bind("input keyup keydown keypress change blur", function() { if ($(this).val() != jQuery.data(this, "lastvalue")) { func(); } jQuery.data(this, "lastvalue", $(this).val()); }); $(id).bind("focus", function() { jQuery.data(this, "lastvalue", $(this).val()); }); } function crChange(e) { var key_coin = getCoinFromKey(bip32_source_key); e.preventDefault(); coin = $(this).attr("id"); ADDRESS_URL_PREFIX = $(this).attr('href'); $('#crName').text($(this).text()); $('#crSelect').dropdown('toggle'); if((coin == "bch_main" || coin == "bch_test") && !acknowledged_bch_is_experimental) { alert("Warning!\n\nBitcoin Cash support is NEW and EXPERIMENTAL.\n\nPlease verify your addresses with other services before using!"); acknowledged_bch_is_experimental = true; } if( gen_from == 'pass' && bip32_source_key === null ) { updateSourcePassphrase(); } else if( bip32_source_key !== null ) { if( coin != key_coin ) { // key is changing to another realm.. var is_private = (bip32_source_key.version == key_coin.bip32_private); var is_public = (bip32_source_key.version == key_coin.bip32_public); if( is_public ) { bip32_source_key.version = COINS[coin].bip32_public; bip32_source_key.build_extended_public_key(); $("#bip32_source_key").val(bip32_source_key.extended_public_key_string("base58")); } else if( is_private ) { bip32_source_key.version = COINS[coin].bip32_private; bip32_source_key.build_extended_public_key(); bip32_source_key.build_extended_private_key(); $("#bip32_source_key").val(bip32_source_key.extended_private_key_string("base58")); } } updateSourceKey(); } return false; } // -- web worker for hashing passphrase -- function hash_worker_message(e) { // ignore the hash worker if(!hash_worker_working) return; var m = e.data; switch(m.cmd) { case 'progress': $("#bip32_hashing_progress_bar").width('' + m.progress + "%"); break; case 'done': $("#bip32_hashing_progress_bar").width('100%'); $("#bip32_hashing_style").removeClass("active"); $("#cancel_hash_worker").attr('disabled', true); hash_worker_working = false; bip32_passphrase_hash = m.result; updatePassphraseHash(); break; } console.log(m); } function start_hash_worker(passphrase) { if( hash_worker === null ) { hash_worker = new Worker("js/hash_worker.js"); hash_worker.addEventListener('message', hash_worker_message, false); } bip32_passphrase_hash = null; bip32_source_key = null; $("#bip32_source_key").val(''); updateSourceKey(); updateResult(); $("#bip32_hashing_progress_bar").css('width', '0%'); $("#bip32_hashing_style").addClass("active"); hash_worker_working = true; $("#cancel_hash_worker").attr('disabled', false); hash_worker.postMessage({"cmd": "start", "bip32_source_passphrase": passphrase}); } function stop_hash_worker() { $("#cancel_hash_worker").attr('disabled', true); hash_worker_working = false; $("#bip32_hashing_progress_bar").css("width", "0%"); if( hash_worker != null ) { hash_worker.postMessage({"cmd": "stop"}); } } function revealKeyQRClicked() { $("#revealKeyQR").hide(); qrCode = qrcode(5, 'Q'); qrCode.addData($("#derived_private_key_wif").val()); qrCode.make(); $('#genKeyQR').html(qrCode.createImgTag(4)); $('#genKeyURL').attr('title','Click again to hide'); $('#genKeyURL').show(); } function hideKeyQRClicked() { $('#genKeyURL').hide(); $("#revealKeyQR").show(); } $(document).ready( function() { if (window.location.hash) $('#tab-' + window.location.hash.substr(1).split('?')[0]).tab('show'); $('a[data-toggle="tab"]').on('click', function (e) { window.location.hash = $(this).attr('href'); }); // bip32 $('#gen_from label input').on('change', onUpdateGenFrom ); updateGenFrom(); $("#bip32_source_passphrase").val("crazy horse battery staple"); $("#bip32_source_key").val("xprv9s21ZrQH143K2JF8RafpqtKiTbsbaxEeUaMnNHsm5o6wCW3z8ySyH4UxFVSfZ8n7ESu7fgir8imbZKLYVBxFPND1pniTZ81vKfd45EHKX73"); onInput("#bip32_source_passphrase", onUpdateSourcePassphrase); $("#checkbox_show_passphrase").on('change', onShowPassphraseChanged ); $("#cancel_hash_worker").on('click', onCancelHashWorkerClicked); onInput("#bip32_source_key", onUpdateSourceKey); $("#bip32_hashing_progress_bar").width('100%'); $("#cancel_hash_worker").attr('disabled', true); updateSourceKey(); $('#bip32_derivation_path').on('change', onUpdateDerivationPath); onInput("#bip32_custom_path", onUpdateCustomPath); onInput("#account_index", onAccountIndexChanged); onInput("#keypair_index", onKeypairIndexChanged); updateDerivationPath(); // currency select $('#crCurrency ul li a').on('click', crChange); $('#genKeyURL').hide(); $("#revealKeyQR").on('click', revealKeyQRClicked); $('#genKeyURL').on('click', hideKeyQRClicked); }); })(jQuery);