Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up signing many bitcoin cash inputs #1

Open
wants to merge 5 commits into
base: bitcoincash
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ <h2>Settings <small> making coinb.in even better!</small></h2>
<p class="text-muted">Select which network you'd like to use for key pair generation.</p>
<select class="form-control" id="coinjs_coin">
<option value="bitcoin_mainnet" rel="0x00;0x80;0x05;0x488b21e;0x488ade4;coinb.in;coinb.in">Bitcoin (mainnet)</option>
<option value="bitcoin-cash_mainnet" rel="0x00;0x80;0x05;0x488b21e;0x488ade4;blockdozer.com_bitcoincash;blockdozer.com_bitcoincash">Bitcoin Cash (mainnet)</option>
<option value="litecoin_mainnet" rel="0x30;0xb0;0x05;0x019da462;0x019d9cfe;blockr.io_litecoin;chain.so_litecoin">Litecoin (mainnet)</option>
<option value="dogecoin_mainnet" rel="0x1e;0x9e;0x16;0x0827421e;0x089944e4;chain.so_dogecoin;chain.so_dogecoin">Dogecoin (mainnet)</option>
<option value="carboncoin_mainnet" rel="0x2f;0xaf;0x05;0x488b21e;0x488ade4;cryptoid.info_carboncoin;cryptoid.info_carboncoin">Carboncoin (mainnet)</option>
Expand Down Expand Up @@ -1059,6 +1060,7 @@ <h2>Settings <small> making coinb.in even better!</small></h2>
<p class="text-muted">Select the network you wish to broadcast the transaction via</p>
<select class="form-control" id="coinjs_broadcast">
<option value="coinb.in">coinb.in (Bitcoin mainnet)</option>
<option value="blockdozer.com_bitcoincash">Blockdozer.com (Bitcoin Cash Mainnet)</option>
<option value="blockr.io_bitcoinmainnet"> Blockr.io (Bitcoin mainnet)</option>
<option value="chain.so_bitcoinmainnet"> Chain.so (Bitcoin mainnet)</option>
<option value="blockcypher_bitcoinmainnet"> Blockcypher.com (Bitcoin mainnet)</option>
Expand All @@ -1078,6 +1080,7 @@ <h2>Settings <small> making coinb.in even better!</small></h2>
<p class="text-muted">Select the network you wish to retreive your unspent inputs from</p>
<select class="form-control" id="coinjs_utxo">
<option value="coinb.in">coinb.in (Bitcoin mainnet)</option>
<option value="blockdozer.com_bitcoincash">Blockdozer.com (Bitcoin Cash Mainnet)</option>
<option value="blockr.io_bitcoinmainnet"> Blockr.io (Bitcoin mainnet)</option>
<option value="chain.so_litecoin"> Chain.so (Litecoin)</option>
<option value="chain.so_dogecoin"> Chain.so (Dogecoin)</option>
Expand Down
173 changes: 147 additions & 26 deletions js/coin.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@
return coinjs.base58encode(r.concat(checksum));
}

/* provide a redeemscript and return address */
coinjs.redeemscript2address = function(h){
var r = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(h), {asBytes: true}));
r.unshift(coinjs.multisig);
var hash = Crypto.SHA256(Crypto.SHA256(r, {asBytes: true}), {asBytes: true});
var checksum = hash.slice(0, 4);
return coinjs.base58encode(r.concat(checksum));
}

/* provide a scripthash and return address */
coinjs.scripthash2address = function(h){
var x = Crypto.util.hexToBytes(h);
Expand Down Expand Up @@ -776,11 +785,12 @@
r.block = null;

/* add an input to a transaction */
r.addinput = function(txid, index, script, sequence){
r.addinput = function(txid, index, script, sequence, value){
var o = {};
o.outpoint = {'hash':txid, 'index':index};
o.script = coinjs.script(script||[]);
o.sequence = sequence || ((r.lock_time==0) ? 4294967295 : 0);
o.value = value ? new BigInteger('' + Math.round((value*1) * 1e8), 10) : null;
return this.ins.push(o);
}

Expand Down Expand Up @@ -880,10 +890,11 @@
var txhash = (u.getElementsByTagName("tx_hash")[0].childNodes[0].nodeValue).match(/.{1,2}/g).reverse().join("")+'';
var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue;
var script = u.getElementsByTagName("script")[0].childNodes[0].nodeValue;
var unspentValue = u.getElementsByTagName("value")[0].childNodes[0].nodeValue*1;

self.addinput(txhash, n, script);
self.addinput(txhash, n, script, unspentValue);

value += u.getElementsByTagName("value")[0].childNodes[0].nodeValue*1;
value += unspentValue
total++;
}

Expand Down Expand Up @@ -931,10 +942,13 @@
/* SIGHASH : For more info on sig hashs see https://en.bitcoin.it/wiki/OP_CHECKSIG
and https://bitcoin.org/en/developer-guide#signature-hash-type */

if(shType == 1){
var shMask = shType & 0xe0;
var shValue = shType & 0x1f;

if(shValue == 1){
//SIGHASH_ALL 0x01

} else if(shType == 2){
} else if(shValue == 2){
//SIGHASH_NONE 0x02
clone.outs = [];
for (var i = 0; i < clone.ins.length; i++) {
Expand All @@ -943,7 +957,7 @@
}
}

} else if(shType == 3){
} else if(shValue == 3){

//SIGHASH_SINGLE 0x03
clone.outs.length = index + 1;
Expand All @@ -959,37 +973,129 @@
}
}

} else if (shType >= 128){
//SIGHASH_ANYONECANPAY 0x80
clone.ins = [clone.ins[index]];
}

if(shType==129){
// SIGHASH_ALL + SIGHASH_ANYONECANPAY
var currentInput = clone.ins[index];

} else if(shType==130){
// SIGHASH_NONE + SIGHASH_ANYONECANPAY
clone.outs = [];
if (shMask & 0x80){
//SIGHASH_ANYONECANPAY 0x80
clone.ins = [clone.ins[index]];
}

} else if(shType==131){
// SIGHASH_SINGLE + SIGHASH_ANYONECANPAY
clone.outs.length = index + 1;
for(var i = 0; i < index; i++){
clone.outs[i].value = -1;
clone.outs[i].script.buffer = [];
}
}
var buffer;
if (!(shMask & 0x40)){
buffer = Crypto.util.hexToBytes(clone.serialize());
buffer = buffer.concat(coinjs.numToBytes(parseInt(shType), 4));
} else { /* SIGHASH_FORKID is flagged, perform BIP143 hashing. */
buffer = [];
buffer = buffer.concat(coinjs.numToBytes(parseInt(clone.version),4));
buffer = buffer.concat(coinjs.hash256(clone.getPrevouts()));
buffer = buffer.concat(coinjs.hash256(clone.getSequences()));
buffer = buffer.concat(Crypto.util.hexToBytes(currentInput.outpoint.hash).reverse());
buffer = buffer.concat(coinjs.numToBytes(parseInt(currentInput.outpoint.index),4));
buffer = buffer.concat(coinjs.numToVarInt(currentInput.script.buffer.length));
buffer = buffer.concat(currentInput.script.buffer);
buffer = buffer.concat(coinjs.numToBytes(currentInput.value,8));
buffer = buffer.concat(coinjs.numToBytes(parseInt(currentInput.sequence),4));
buffer = buffer.concat(coinjs.hash256(clone.getOutputs()));
buffer = buffer.concat(coinjs.numToBytes(parseInt(this.lock_time),4));
buffer = buffer.concat(coinjs.numToBytes(parseInt(shType), 4));
}

var buffer = Crypto.util.hexToBytes(clone.serialize());
buffer = buffer.concat(coinjs.numToBytes(parseInt(shType), 4));
var hash = Crypto.SHA256(buffer, {asBytes: true});
var r = Crypto.util.bytesToHex(Crypto.SHA256(hash, {asBytes: true}));
var r = Crypto.util.bytesToHex(coinjs.hash256(buffer));
return r;
} else {
return false;
}
}

r.getPrevouts = function() {
var buffer = [];
for (var i = 0; i < this.ins.length; i++) {
var txin = this.ins[i];
buffer = buffer.concat(Crypto.util.hexToBytes(txin.outpoint.hash).reverse());
buffer = buffer.concat(coinjs.numToBytes(parseInt(txin.outpoint.index),4));
}
return buffer;
}

r.getSequences = function() {
var buffer = [];
for (var i = 0; i < this.ins.length; i++) {
var txin = this.ins[i];
buffer = buffer.concat(coinjs.numToBytes(parseInt(txin.sequence),4));
}
return buffer;
}

r.getOutputs = function() {
var buffer = [];
for (var i = 0; i < this.outs.length; i++) {
var txout = this.outs[i];
buffer = buffer.concat(coinjs.numToBytes(txout.value,8));
var scriptBytes = txout.script.buffer;
buffer = buffer.concat(coinjs.numToVarInt(scriptBytes.length));
buffer = buffer.concat(scriptBytes);
}
return buffer;
}

r.extractAddress = function(index) {
var utxo_script = this.extractScriptKey(index);
var address;
if (utxo_script['type'] == 'scriptpubkey') {
address = coinjs.scripthash2address(utxo_script['script'].slice(6, 46));
} else if (utxo_script['type'] == 'multisig') {
address = coinjs.redeemscript2address(utxo_script['script']); // hash the redeemscript
} else if (utxo_script['type'] == 'hodl') {
alert('Bitcoin Cash hodl addresses not supported yet. Sorry.');
return false;
} else {
alert('Can not retreive input values for Bitcoin Cash signatures.');
return false;
}
return address;
}

r.getInputValues = function() {
var self = this;
self.ins.forEach(function(input, i) {
var utxo_txid = input.outpoint.hash;
var utxo_index = input.outpoint.index;
var utxo_address = self.extractAddress(i);
if (!input.value) {
$.ajax ({
type: "GET",
cache: false,
async: false,
url: "http://blockdozer.com/insight-api/addr/"+utxo_address+"/utxo",
dataType: "json",
error: function(data) {
alert('Couldn\'t get values for inputs. Bitcoin Cash will not sign correctly.');
},
success: function(data) {
if(data[0] == undefined)
{
alert('Can not retreive input values for Bitcoin Cash signatures.');
}
if((data[0].address && data[0].txid) && data[0].address === utxo_address) {
data.forEach(function(unspent) {
self.ins.forEach(function(input) {
if (input.outpoint.hash === unspent.txid
&& input.outpoint.index === unspent.vout) {
input.value = new BigInteger('' + Math.round((unspent.amount*1) * 1e8), 10);
}
})
});
} else {
alert('Can not retreive input values for Bitcoin Cash signatures.');
}
}
});
}
});
}

/* extract the scriptSig, used in the transactionHash() function */
r.extractScriptKey = function(index) {
if(this.ins[index]){
Expand Down Expand Up @@ -1045,7 +1151,18 @@
return sequence;
}

var host = $("#coinjs_broadcast option:selected").val();
var isBitcoinCash = (host == 'blockdozer.com_bitcoincash')

var shType = sigHashType || 1;

if (isBitcoinCash) {
/* Add SIGHASH_FORKID by default for Bitcoin Cash */
shType = shType | 0x40;

this.getInputValues();
}

var hash = Crypto.util.hexToBytes(this.transactionHash(index, shType));

if(hash){
Expand Down Expand Up @@ -1542,6 +1659,10 @@
else return bytes[0] + 256 * coinjs.bytesToNum(bytes.slice(1));
}

coinjs.hash256 = function(bytes) {
return Crypto.SHA256(Crypto.SHA256(bytes, {asBytes: true}), {asBytes: true});
}

coinjs.uint = function(f, size) {
if (f.length < size)
throw new Error("not enough data");
Expand Down
72 changes: 72 additions & 0 deletions js/coinbin.js
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,8 @@ $(document).ready(function() {
listUnspentChainso_Dogecoin(redeem);
} else if(host=='cryptoid.info_carboncoin'){
listUnspentCryptoidinfo_Carboncoin(redeem);
} else if(host=='blockdozer.com_bitcoincash'){
listUnspentBlockdozer_bitcoincash(redeem);
} else {
listUnspentDefault(redeem);
}
Expand Down Expand Up @@ -951,6 +953,46 @@ $(document).ready(function() {

}

/* retrieve unspent data from blockdozer.com (no https available) for bitcoin cash */
function listUnspentBlockdozer_bitcoincash(redeem) {

$.ajax ({
type: "GET",
cache: false,
url: "http://blockdozer.com/insight-api/addr/"+redeem.addr+"/utxo",
dataType: "json",
error: function(data) {
$("#redeemFromStatus").removeClass('hidden').html('<span class="glyphicon glyphicon-exclamation-sign"></span> Unexpected error, unable to retrieve unspent outputs!');
},
success: function(data) {
if(data[0] == undefined)
{
var json = '[{"address":"'+redeem.addr+'","txid":"[]"}]';
data = $.parseJSON(json);
}
if((data[0].address && data[0].txid) && data[0].address==redeem.addr){
$("#redeemFromAddress").removeClass('hidden').html('<span class="glyphicon glyphicon-info-sign"></span> Retrieved unspent inputs from address <a href="http://blockdozer.com/insight/address/'+redeem.addr+'" target="_blank">'+redeem.addr+'</a>');
for(var i in data){
var o = data[i];
var tx = ((""+o.txid).match(/.{1,2}/g).reverse()).join("")+'';
if(tx.match(/^[a-f0-9]+$/)){
var n = o.vout;
var script = (redeem.isMultisig==true) ? $("#redeemFrom").val() : o.scriptPubKey;
var amount = o.amount;
addOutput(tx, n, script, amount);
}
}
} else {
$("#redeemFromStatus").removeClass('hidden').html('<span class="glyphicon glyphicon-exclamation-sign"></span> Unexpected error, unable to retrieve unspent outputs.');
}
},
complete: function(data, status) {
$("#redeemFromBtn").html("Load").attr('disabled',false);
totalInputAmount();
}
});
}

/* retrieve unspent data from chain.so for dogecoin */
function listUnspentChainso_Dogecoin(redeem){
$.ajax ({
Expand Down Expand Up @@ -1100,6 +1142,32 @@ $(document).ready(function() {
});
}

function rawSubmitBlockDozer_BitcoinCash(thisbtn) {
$(thisbtn).val('Please wait, loading...').attr('disabled',true);
$.ajax ({
type: "POST",
url: "http://blockdozer.com/insight-api/tx/send",
data: {"rawtx":$("#rawTransaction").val()},
dataType: "json",
error: function(data) {
var r = data.responseText;
r = (r!='') ? r : ' Failed to broadcast'; // build response
$("#rawTransactionStatus").addClass('alert-danger').removeClass('alert-success').removeClass("hidden").html(r).prepend('<span class="glyphicon glyphicon-exclamation-sign"></span>');
},
success: function(data) {
if(data.txid && data.txid.length > 0){
$("#rawTransactionStatus").addClass('alert-success').removeClass('alert-danger').removeClass("hidden").html(' Txid: '+data.txid);
} else {
$("#rawTransactionStatus").addClass('alert-danger').removeClass('alert-success').removeClass("hidden").html(' Unexpected error, please try again').prepend('<span class="glyphicon glyphicon-exclamation-sign"></span>');
}
},
complete: function(data, status) {
$("#rawTransactionStatus").fadeOut().fadeIn();
$(thisbtn).val('Submit').attr('disabled',false);
}
});
}

// broadcast transaction via blockr.io (mainnet)
function rawSubmitBlockrio_BitcoinMainnet(thisbtn){
$(thisbtn).val('Please wait, loading...').attr('disabled',true);
Expand Down Expand Up @@ -1736,6 +1804,10 @@ $(document).ready(function() {
$("#rawSubmitBtn").click(function(){
rawSubmitcryptoid_Carboncoin(this);
});
} else if(host=="blockdozer.com_bitcoincash"){
$("#rawSubmitBtn").click(function(){
rawSubmitBlockDozer_BitcoinCash(this);
});
} else {
$("#rawSubmitBtn").click(function(){
rawSubmitDefault(this); // revert to default
Expand Down