Files
bip32/js/brainwallet.js
2024-08-21 09:55:33 +02:00

646 lines
23 KiB
JavaScript

(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("<b>" + key_coin.name + " Master Private Key</b>");
} else {
$("#bip32_key_info_title").html("<b>" + key_coin.name + " Master Public Key</b>");
}
} else {
if( bip32_source_key.has_private_key ) {
$("#bip32_key_info_title").html("<b>" + key_coin.name + " Derived Private Key</b>");
} else {
$("#bip32_key_info_title").html("<b>" + key_coin.name + " Derived Public Key</b>");
}
}
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);