a209aac217
iterative hashing without attempting to preserve entropy is not best practice. I lowered iteration count to keep the same amount of time for the function. with (x).length, it was almost 16 times slower, and if divided by 16 the whole process took the same amount of time. Considering the length is usually around 1600 for me, that means somewhere around 100 iterations including the entirety of x in each hash concatenated with the current iteration of r.
945 lines
27 KiB
JavaScript
945 lines
27 KiB
JavaScript
/*
|
|
Coinjs 0.01 beta by OutCast3k{at}gmail.com
|
|
A bitcoin framework loosely based on bitcoinjs.
|
|
|
|
http://github.com/OutCast3k/coinjs or http://coinb.in/coinjs
|
|
*/
|
|
|
|
(function () {
|
|
|
|
var coinjs = window.coinjs = function () { };
|
|
|
|
/* public vars */
|
|
coinjs.pub = 0x00;
|
|
coinjs.priv = 0x80;
|
|
coinjs.multisig = 0x05;
|
|
coinjs.compressed = false;
|
|
|
|
/* other vars */
|
|
coinjs.developer = '1CWHWkTWaq1K5hevimJia3cyinQsrgXUvg';
|
|
|
|
/* bit(coinb.in) api vars */
|
|
coinjs.host = ('https:'==document.location.protocol?'https://':'http://')+'coinb.in/api/';
|
|
coinjs.uid = '1';
|
|
coinjs.key = '12345678901234567890123456789012';
|
|
|
|
/* start of address functions */
|
|
|
|
/* generate a private and public keypair, with address and WIF address */
|
|
coinjs.newKeys = function(string){
|
|
var privkey = (string) ? Crypto.SHA256(string) : this.newPrivkey();
|
|
var pubkey = this.newPubkey(privkey);
|
|
return {
|
|
'privkey': privkey,
|
|
'pubkey': pubkey,
|
|
'address': this.pubkey2address(pubkey),
|
|
'wif': this.privkey2wif(privkey),
|
|
'compressed': this.compressed
|
|
};
|
|
}
|
|
|
|
/* generate a new random private key */
|
|
coinjs.newPrivkey = function(){
|
|
var x = window.location;
|
|
x += (window.screen.height * window.screen.width * window.screen.colorDepth);
|
|
x += coinjs.random(64);
|
|
x += (window.screen.availHeight * window.screen.availWidth * window.screen.pixelDepth);
|
|
x += navigator.language;
|
|
x += window.history.length;
|
|
x += coinjs.random(64);
|
|
x += navigator.userAgent;
|
|
x += 'coinb.in';
|
|
x += (Crypto.util.randomBytes(64)).join("");
|
|
x += x.length;
|
|
var dateObj = new Date();
|
|
x += dateObj.getTimezoneOffset();
|
|
x += coinjs.random(64);
|
|
x += x+''+x;
|
|
var r = x;
|
|
for(i=0;i<(x).length/16;i++){
|
|
r = Crypto.SHA256(r.concat(x));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* generate a public key from a private key */
|
|
coinjs.newPubkey = function(hash){
|
|
var privateKeyBigInt = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(hash));
|
|
var curve = EllipticCurve.getSECCurveByName("secp256k1");
|
|
|
|
var curvePt = curve.getG().multiply(privateKeyBigInt);
|
|
var x = curvePt.getX().toBigInteger();
|
|
var y = curvePt.getY().toBigInteger();
|
|
|
|
var publicKeyBytes = EllipticCurve.integerToBytes(x, 32);
|
|
publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y,32));
|
|
publicKeyBytes.unshift(0x04);
|
|
|
|
if(coinjs.compressed==true){
|
|
var publicKeyBytesCompressed = EllipticCurve.integerToBytes(x,32)
|
|
if (y.isEven()){
|
|
publicKeyBytesCompressed.unshift(0x02)
|
|
} else {
|
|
publicKeyBytesCompressed.unshift(0x03)
|
|
}
|
|
return Crypto.util.bytesToHex(publicKeyBytesCompressed);
|
|
} else {
|
|
return Crypto.util.bytesToHex(publicKeyBytes);
|
|
}
|
|
}
|
|
|
|
/* provide a public key and return address */
|
|
coinjs.pubkey2address = function(h){
|
|
var r = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(h), {asBytes: true}));
|
|
r.unshift(coinjs.pub);
|
|
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);
|
|
x.unshift(coinjs.pub);
|
|
var r = x;
|
|
r = Crypto.SHA256(Crypto.SHA256(r,{asBytes: true}),{asBytes: true});
|
|
var checksum = r.slice(0,4);
|
|
return coinjs.base58encode(x.concat(checksum));
|
|
}
|
|
|
|
/* new multisig address, provide the pubkeys AND required signatures to release the funds */
|
|
coinjs.pubkeys2MultisigAddress = function(pubkeys, required) {
|
|
var s = coinjs.script();
|
|
s.writeOp(81 + (required*1) - 1); //OP_1
|
|
for (var i = 0; i < pubkeys.length; ++i) {
|
|
s.writeBytes(Crypto.util.hexToBytes(pubkeys[i]));
|
|
}
|
|
s.writeOp(81 + pubkeys.length - 1); //OP_1
|
|
s.writeOp(174); //OP_CHECKMULTISIG
|
|
var x = ripemd160(Crypto.SHA256(s.buffer, {asBytes: true}), {asBytes: true});
|
|
x.unshift(coinjs.multisig);
|
|
var r = x;
|
|
r = Crypto.SHA256(Crypto.SHA256(r, {asBytes: true}), {asBytes: true});
|
|
var checksum = r.slice(0,4);
|
|
var redeemScript = Crypto.util.bytesToHex(s.buffer);
|
|
var address = coinjs.base58encode(x.concat(checksum));
|
|
return {'address':address, 'redeemScript':redeemScript};
|
|
}
|
|
|
|
/* provide a privkey and return an WIF */
|
|
coinjs.privkey2wif = function(h){
|
|
var r = Crypto.util.hexToBytes(h);
|
|
|
|
if(coinjs.compressed==true){
|
|
r.push(0x01);
|
|
}
|
|
|
|
r.unshift(coinjs.priv);
|
|
var hash = Crypto.SHA256(Crypto.SHA256(r, {asBytes: true}), {asBytes: true});
|
|
var checksum = hash.slice(0, 4);
|
|
|
|
return coinjs.base58encode(r.concat(checksum));
|
|
}
|
|
|
|
/* convert a wif key back to a private key */
|
|
coinjs.wif2privkey = function(wif){
|
|
var compressed = false;
|
|
var decode = coinjs.base58decode(wif);
|
|
var key = decode.slice(0, decode.length-4);
|
|
key = key.slice(1, key.length);
|
|
if(key.length>=33 && key[key.length-1]==0x01){
|
|
key = key.slice(0, key.length-1);
|
|
compressed = true;
|
|
}
|
|
return {'privkey': Crypto.util.bytesToHex(key), 'compressed':compressed};
|
|
}
|
|
|
|
/* convert a wif to a pubkey */
|
|
coinjs.wif2pubkey = function(wif){
|
|
var compressed = coinjs.compressed;
|
|
var r = coinjs.wif2privkey(wif);
|
|
coinjs.compressed = r['compressed'];
|
|
var pubkey = coinjs.newPubkey(r['privkey']);
|
|
coinjs.compressed = compressed;
|
|
return {'pubkey':pubkey,'compressed':r['compressed']};
|
|
}
|
|
|
|
/* convert a wif to a address */
|
|
coinjs.wif2address = function(wif){
|
|
var r = coinjs.wif2pubkey(wif);
|
|
return {'address':coinjs.pubkey2address(r['pubkey']), 'compressed':r['compressed']};
|
|
}
|
|
|
|
/* decode or validate an address and return the hash */
|
|
coinjs.addressDecode = function(addr){
|
|
try {
|
|
var bytes = coinjs.base58decode(addr);
|
|
var front = bytes.slice(0, bytes.length-4);
|
|
var back = bytes.slice(bytes.length-4);
|
|
var checksum = Crypto.SHA256(Crypto.SHA256(front, {asBytes: true}), {asBytes: true}).slice(0, 4);
|
|
if (checksum+"" == back+"") {
|
|
var o = front.slice(1);
|
|
o.version = front[0];
|
|
return o;
|
|
} else {
|
|
return false;
|
|
}
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* retreive the balance from a given address */
|
|
coinjs.addressBalance = function(address, callback){
|
|
coinjs.ajax(coinjs.host+'?uid='+coinjs.uid+'&key='+coinjs.key+'&setmodule=addresses&request=bal&address='+address+'&r='+Math.random(), callback, "GET");
|
|
}
|
|
|
|
/* decompress an compressed public key */
|
|
coinjs.pubkeydecompress = function(pubkey) {
|
|
var curve = EllipticCurve.getSECCurveByName("secp256k1");
|
|
try {
|
|
var pt = curve.curve.decodePointHex(pubkey);
|
|
var x = pt.getX().toBigInteger();
|
|
var y = pt.getY().toBigInteger();
|
|
|
|
var publicKeyBytes = EllipticCurve.integerToBytes(x, 32);
|
|
publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y,32));
|
|
publicKeyBytes.unshift(0x04);
|
|
return Crypto.util.bytesToHex(publicKeyBytes);
|
|
} catch (e) {
|
|
// console.log(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/* start of script functions */
|
|
|
|
coinjs.script = function(data) {
|
|
var r = {};
|
|
|
|
if(!data){
|
|
r.buffer = [];
|
|
} else if ("string" == typeof data) {
|
|
r.buffer = Crypto.util.hexToBytes(data);
|
|
} else if (coinjs.isArray(data)) {
|
|
r.buffer = data;
|
|
} else if (data instanceof coinjs.script) {
|
|
r.buffer = r.buffer;
|
|
} else {
|
|
r.buffer = data;
|
|
}
|
|
|
|
/* parse buffer array */
|
|
r.parse = function () {
|
|
|
|
var self = this;
|
|
r.chunks = [];
|
|
var i = 0;
|
|
|
|
function readChunk(n) {
|
|
self.chunks.push(self.buffer.slice(i, i + n));
|
|
i += n;
|
|
};
|
|
|
|
while (i < this.buffer.length) {
|
|
var opcode = this.buffer[i++];
|
|
if (opcode >= 0xF0) {
|
|
opcode = (opcode << 8) | this.buffer[i++];
|
|
}
|
|
|
|
var len;
|
|
if (opcode > 0 && opcode < 76) { //OP_PUSHDATA1
|
|
readChunk(opcode);
|
|
} else if (opcode == 76) { //OP_PUSHDATA1
|
|
len = this.buffer[i++];
|
|
readChunk(len);
|
|
} else if (opcode == 77) { //OP_PUSHDATA2
|
|
len = (this.buffer[i++] << 8) | this.buffer[i++];
|
|
readChunk(len);
|
|
} else if (opcode == 78) { //OP_PUSHDATA4
|
|
len = (this.buffer[i++] << 24) | (this.buffer[i++] << 16) | (this.buffer[i++] << 8) | this.buffer[i++];
|
|
readChunk(len);
|
|
} else {
|
|
this.chunks.push(opcode);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/* decode the redeemscript of a multisignature transaction */
|
|
r.decodeRedeemScript = function(script){
|
|
var r = false;
|
|
try {
|
|
var s = coinjs.script(Crypto.util.hexToBytes(script));
|
|
if((s.chunks.length>=3) && s.chunks[s.chunks.length-1] == 174){//OP_CHECKMULTISIG
|
|
r = {};
|
|
r.signaturesRequired = s.chunks[0]-80;
|
|
var pubkeys = [];
|
|
for(var i=1;i<s.chunks.length-2;i++){
|
|
pubkeys.push(Crypto.util.bytesToHex(s.chunks[i]));
|
|
}
|
|
r.pubkeys = pubkeys;
|
|
var multi = coinjs.pubkeys2MultisigAddress(pubkeys, r.signaturesRequired);
|
|
r.address = multi['address'];
|
|
}
|
|
} catch(e) {
|
|
// console.log(e);
|
|
r = false;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* create output script to spend */
|
|
r.spendToScript = function(address){
|
|
var addr = coinjs.addressDecode(address);
|
|
var s = coinjs.script();
|
|
if(addr.version==5){ // multisig address
|
|
s.writeOp(169); //OP_HASH160
|
|
s.writeBytes(addr);
|
|
s.writeOp(135); //OP_EQUAL
|
|
} else { // regular address
|
|
s.writeOp(118); //OP_DUP
|
|
s.writeOp(169); //OP_HASH160
|
|
s.writeBytes(addr);
|
|
s.writeOp(136); //OP_EQUALVERIFY
|
|
s.writeOp(172); //OP_CHECKSIG
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/* geneate a (script) pubkey hash of the address - used for when signing */
|
|
r.pubkeyHash = function(address) {
|
|
var addr = coinjs.addressDecode(address);
|
|
var s = coinjs.script();
|
|
s.writeOp(118);//OP_DUP
|
|
s.writeOp(169);//OP_HASH160
|
|
s.writeBytes(addr);
|
|
s.writeOp(136);//OP_EQUALVERIFY
|
|
s.writeOp(172);//OP_CHECKSIG
|
|
return s;
|
|
}
|
|
|
|
/* write to buffer */
|
|
r.writeOp = function(op){
|
|
this.buffer.push(op);
|
|
this.chunks.push(op);
|
|
return true;
|
|
}
|
|
|
|
/* write bytes to buffer */
|
|
r.writeBytes = function(data){
|
|
if (data.length < 76) { //OP_PUSHDATA1
|
|
this.buffer.push(data.length);
|
|
} else if (data.length <= 0xff) {
|
|
this.buffer.push(76); //OP_PUSHDATA1
|
|
this.buffer.push(data.length);
|
|
} else if (data.length <= 0xffff) {
|
|
this.buffer.push(77); //OP_PUSHDATA2
|
|
this.buffer.push(data.length & 0xff);
|
|
this.buffer.push((data.length >>> 8) & 0xff);
|
|
} else {
|
|
this.buffer.push(78); //OP_PUSHDATA4
|
|
this.buffer.push(data.length & 0xff);
|
|
this.buffer.push((data.length >>> 8) & 0xff);
|
|
this.buffer.push((data.length >>> 16) & 0xff);
|
|
this.buffer.push((data.length >>> 24) & 0xff);
|
|
}
|
|
this.buffer = this.buffer.concat(data);
|
|
this.chunks.push(data);
|
|
return true;
|
|
}
|
|
|
|
r.parse();
|
|
return r;
|
|
}
|
|
|
|
/* start of transaction functions */
|
|
|
|
/* create a new transaction object */
|
|
coinjs.transaction = function() {
|
|
|
|
var r = {};
|
|
r.version = 1;
|
|
r.lock_time = 0;
|
|
r.ins = [];
|
|
r.outs = [];
|
|
r.timestamp = null;
|
|
r.block = null;
|
|
|
|
/* add an input to a transaction */
|
|
r.addinput = function(txid, index, script){
|
|
var o = {};
|
|
o.outpoint = {'hash':txid, 'index':index};
|
|
o.script = coinjs.script(script||[]);
|
|
o.sequence = (r.lock_time==0) ? 4294967295 : 0;
|
|
return this.ins.push(o);
|
|
}
|
|
|
|
/* add an output to a transaction */
|
|
r.addoutput = function(address, value){
|
|
var o = {};
|
|
o.value = new BigInteger('' + Math.round((value*1) * 1e8), 10);
|
|
var s = coinjs.script();
|
|
o.script = s.spendToScript(address);
|
|
return this.outs.push(o);
|
|
}
|
|
|
|
/* list unspent transactions */
|
|
r.listUnspent = function(address, callback) {
|
|
coinjs.ajax(coinjs.host+'?uid='+coinjs.uid+'&key='+coinjs.key+'&setmodule=addresses&request=unspent&address='+address+'&r='+Math.random(), callback, "GET");
|
|
}
|
|
|
|
/* add unspent to transaction */
|
|
r.addUnspent = function(address, callback){
|
|
var self = this;
|
|
this.listUnspent(address, function(data){
|
|
var s = coinjs.script();
|
|
var pubkeyScript = s.pubkeyHash(address);
|
|
var value = 0;
|
|
var total = 0;
|
|
var x = {};
|
|
|
|
if (window.DOMParser) {
|
|
parser=new DOMParser();
|
|
xmlDoc=parser.parseFromString(data,"text/xml");
|
|
} else {
|
|
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
|
|
xmlDoc.async=false;
|
|
xmlDoc.loadXML(data);
|
|
}
|
|
|
|
var unspent = xmlDoc.getElementsByTagName("unspent")[0];
|
|
|
|
for(i=1;i<=unspent.childElementCount;i++){
|
|
var u = xmlDoc.getElementsByTagName("unspent_"+i)[0]
|
|
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;
|
|
|
|
self.addinput(txhash, n, script);
|
|
|
|
value += u.getElementsByTagName("value")[0].childNodes[0].nodeValue*1;
|
|
total++;
|
|
}
|
|
|
|
x.unspent = $(xmlDoc).find("unspent");
|
|
x.value = value;
|
|
x.total = total;
|
|
return callback(x);
|
|
});
|
|
}
|
|
|
|
/* add unspent and sign */
|
|
r.addUnspentAndSign = function(wif, callback){
|
|
var self = this;
|
|
var address = coinjs.wif2address(wif);
|
|
self.addUnspent(address['address'], function(data){
|
|
self.sign(wif);
|
|
return callback(data);
|
|
});
|
|
}
|
|
|
|
/* broadcast a transaction */
|
|
r.broadcast = function(callback, txhex){
|
|
var tx = txhex || this.serialize();
|
|
coinjs.ajax(coinjs.host+'?uid='+coinjs.uid+'&key='+coinjs.key+'&setmodule=bitcoin&request=sendrawtransaction&rawtx='+tx+'&r='+Math.random(), callback, "GET");
|
|
}
|
|
|
|
/* generate the transaction hash to sign from a transaction input */
|
|
r.transactionHash = function(index) {
|
|
var clone = coinjs.clone(this);
|
|
if((clone.ins) && clone.ins[index]){
|
|
for (var i = 0; i < clone.ins.length; i++) {
|
|
if(index!=i){
|
|
clone.ins[i].script = coinjs.script();
|
|
}
|
|
}
|
|
|
|
var extract = this.extractScriptKey(index);
|
|
clone.ins[index].script = coinjs.script(extract['script']);
|
|
|
|
var buffer = Crypto.util.hexToBytes(clone.serialize());
|
|
buffer = buffer.concat(coinjs.numToBytes(parseInt(1),4));
|
|
var hash = Crypto.SHA256(buffer, {asBytes: true});
|
|
var r = Crypto.util.bytesToHex(Crypto.SHA256(hash, {asBytes: true}));
|
|
return r;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* extract the scriptSig, used in the transactionHash() function */
|
|
r.extractScriptKey = function(index) {
|
|
if(this.ins[index]){
|
|
if((this.ins[index].script.chunks.length==5) && this.ins[index].script.chunks[4]==172 && coinjs.isArray(this.ins[index].script.chunks[2])){ //OP_CHECKSIG
|
|
// regular scriptPubkey (not signed)
|
|
return {'type':'scriptpubkey', 'signed':'false', 'signatures':0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
|
|
} else if((this.ins[index].script.chunks.length==2) && this.ins[index].script.chunks[0][0]==48){
|
|
// regular scriptPubkey (probably signed)
|
|
return {'type':'scriptpubkey', 'signed':'true', 'signatures':1, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
|
|
} else if (this.ins[index].script.chunks[0]==0 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1][this.ins[index].script.chunks[this.ins[index].script.chunks.length-1].length-1]==174) { // OP_CHECKMULTISIG
|
|
// multisig script, with signature(s) included
|
|
return {'type':'multisig', 'signed':'true', 'signatures':this.ins[index].script.chunks.length-2, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[this.ins[index].script.chunks.length-1])};
|
|
} else if (this.ins[index].script.chunks[0]>=80 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1]==174) { // OP_CHECKMULTISIG
|
|
// multisig script, without signature!
|
|
return {'type':'multisig', 'signed':'false', 'signatures':0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
|
|
} else if (this.ins[index].script.chunks.length==0) {
|
|
// empty
|
|
return {'type':'empty', 'signed':'false', 'signatures':0, 'script': ''};
|
|
} else {
|
|
// something else
|
|
return {'type':'unknown', 'signed':'false', 'signatures':0, 'script':Crypto.util.bytesToHex(this.ins[index].script.buffer)};
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* generate a signature from a transaction hash */
|
|
r.transactionSig = function(index, wif){
|
|
|
|
function serializeSig(r, s) {
|
|
var rBa = r.toByteArraySigned();
|
|
var sBa = s.toByteArraySigned();
|
|
|
|
var sequence = [];
|
|
sequence.push(0x02); // INTEGER
|
|
sequence.push(rBa.length);
|
|
sequence = sequence.concat(rBa);
|
|
|
|
sequence.push(0x02); // INTEGER
|
|
sequence.push(sBa.length);
|
|
sequence = sequence.concat(sBa);
|
|
|
|
sequence.unshift(sequence.length);
|
|
sequence.unshift(0x30); // SEQUENCE
|
|
|
|
return sequence;
|
|
}
|
|
|
|
var hash = Crypto.util.hexToBytes(this.transactionHash(index));
|
|
|
|
if(hash){
|
|
var rng = new SecureRandom();
|
|
var curve = EllipticCurve.getSECCurveByName("secp256k1");
|
|
var key = coinjs.wif2privkey(wif);
|
|
var priv = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(key['privkey']));
|
|
var n = curve.getN();
|
|
var e = BigInteger.fromByteArrayUnsigned(hash);
|
|
do {
|
|
var k = new BigInteger(n.bitLength(), rng).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
|
|
var G = curve.getG();
|
|
var Q = G.multiply(k);
|
|
var r = Q.getX().toBigInteger().mod(n);
|
|
} while (r.compareTo(BigInteger.ZERO) <= 0);
|
|
|
|
var s = k.modInverse(n).multiply(e.add(priv.multiply(r))).mod(n);
|
|
|
|
var sig = serializeSig(r, s);
|
|
sig.push(parseInt(1, 10));
|
|
|
|
return Crypto.util.bytesToHex(sig);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* sign a "standard" input */
|
|
r.signinput = function(index, wif){
|
|
var key = coinjs.wif2pubkey(wif);
|
|
var signature = this.transactionSig(index, wif);
|
|
var s = coinjs.script();
|
|
s.writeBytes(Crypto.util.hexToBytes(signature));
|
|
s.writeBytes(Crypto.util.hexToBytes(key['pubkey']));
|
|
this.ins[index].script = s;
|
|
return true;
|
|
}
|
|
|
|
/* sign a multisig input */
|
|
r.signmultisig = function(index, wif){
|
|
|
|
function scriptListPubkey(redeemScript){
|
|
var r = {};
|
|
for(var i=1;i<redeemScript.chunks.length-2;i++){
|
|
r[i] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(Crypto.util.bytesToHex(redeemScript.chunks[i])));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
function scriptListSigs(scriptSig){
|
|
var r = {};
|
|
if (scriptSig.chunks[0]==0 && scriptSig.chunks[scriptSig.chunks.length-1][scriptSig.chunks[scriptSig.chunks.length-1].length-1]==174){
|
|
for(var i=1;i<scriptSig.chunks.length-1;i++){
|
|
r[i] = scriptSig.chunks[i];
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
var redeemScript = (this.ins[index].script.chunks[this.ins[index].script.chunks.length-1]==174) ? this.ins[index].script.buffer : this.ins[index].script.chunks[this.ins[index].script.chunks.length-1];
|
|
var sighash = Crypto.util.hexToBytes(this.transactionHash(index));
|
|
var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif));
|
|
var s = coinjs.script();
|
|
|
|
s.writeOp(0);
|
|
|
|
if(this.ins[index].script.chunks[this.ins[index].script.chunks.length-1]==174){
|
|
s.writeBytes(signature);
|
|
|
|
} else if (this.ins[index].script.chunks[0]==0 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1][this.ins[index].script.chunks[this.ins[index].script.chunks.length-1].length-1]==174){
|
|
var pubkeyList = scriptListPubkey(coinjs.script(redeemScript));
|
|
var sigsList = scriptListSigs(this.ins[index].script);
|
|
sigsList[coinjs.countObject(sigsList)+1] = signature;
|
|
|
|
for(x in pubkeyList){
|
|
for(y in sigsList){
|
|
if(coinjs.verifySignature(sighash, sigsList[y], pubkeyList[x])){
|
|
s.writeBytes(sigsList[y]);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
s.writeBytes(redeemScript);
|
|
this.ins[index].script = s;
|
|
return true;
|
|
}
|
|
|
|
/* sign inputs */
|
|
r.sign = function(wif){
|
|
for (var i = 0; i < this.ins.length; i++) {
|
|
var d = this.extractScriptKey(i);
|
|
|
|
var w2a = coinjs.wif2address(wif);
|
|
var script = coinjs.script();
|
|
var pubkeyHash = script.pubkeyHash(w2a['address']);
|
|
|
|
if(((d['type'] == 'scriptpubkey' && d['script']==Crypto.util.bytesToHex(pubkeyHash.buffer)) || d['type'] == 'empty') && d['signed'] == "false"){
|
|
this.signinput(i, wif);
|
|
} else if (d['type'] == 'multisig') {
|
|
this.signmultisig(i, wif);
|
|
} else {
|
|
// could not sign
|
|
}
|
|
}
|
|
return this.serialize();
|
|
}
|
|
|
|
/* serialize a transaction */
|
|
r.serialize = function(){
|
|
var buffer = [];
|
|
buffer = buffer.concat(coinjs.numToBytes(parseInt(this.version),4));
|
|
buffer = buffer.concat(coinjs.numToVarInt(this.ins.length));
|
|
|
|
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));
|
|
var scriptBytes = txin.script.buffer;
|
|
buffer = buffer.concat(coinjs.numToVarInt(scriptBytes.length));
|
|
buffer = buffer.concat(scriptBytes);
|
|
buffer = buffer.concat(coinjs.numToBytes(parseInt(txin.sequence),4));
|
|
}
|
|
buffer = buffer.concat(coinjs.numToVarInt(this.outs.length));
|
|
|
|
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);
|
|
}
|
|
|
|
buffer = buffer.concat(coinjs.numToBytes(parseInt(this.lock_time),4));
|
|
return Crypto.util.bytesToHex(buffer);
|
|
}
|
|
|
|
/* deserialize a transaction */
|
|
r.deserialize = function(buffer){
|
|
if (typeof buffer == "string") {
|
|
buffer = Crypto.util.hexToBytes(buffer)
|
|
}
|
|
|
|
var pos = 0;
|
|
var readAsInt = function(bytes) {
|
|
if (bytes == 0) return 0;
|
|
pos++;
|
|
return buffer[pos-1] + readAsInt(bytes-1) * 256;
|
|
}
|
|
|
|
var readVarInt = function() {
|
|
pos++;
|
|
if (buffer[pos-1] < 253) {
|
|
return buffer[pos-1];
|
|
}
|
|
return readAsInt(buffer[pos-1] - 251);
|
|
}
|
|
|
|
var readBytes = function(bytes) {
|
|
pos += bytes;
|
|
return buffer.slice(pos - bytes, pos);
|
|
}
|
|
|
|
var readVarString = function() {
|
|
var size = readVarInt();
|
|
return readBytes(size);
|
|
}
|
|
|
|
var obj = new coinjs.transaction();
|
|
|
|
obj.version = readAsInt(4);
|
|
var ins = readVarInt();
|
|
for (var i = 0; i < ins; i++) {
|
|
obj.ins.push({
|
|
outpoint: {
|
|
hash: Crypto.util.bytesToHex(readBytes(32).reverse()),
|
|
index: readAsInt(4)
|
|
},
|
|
script: coinjs.script(readVarString()),
|
|
sequence: readAsInt(4)
|
|
});
|
|
}
|
|
|
|
var outs = readVarInt();
|
|
for (var i = 0; i < outs; i++) {
|
|
obj.outs.push({
|
|
value: coinjs.bytesToNum(readBytes(8)),
|
|
script: coinjs.script(readVarString())
|
|
});
|
|
}
|
|
|
|
obj.lock_time = readAsInt(4);
|
|
return obj;
|
|
}
|
|
|
|
r.size = function(){
|
|
return ((this.serialize()).length/2).toFixed(0);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/* start of signature vertification functions */
|
|
|
|
coinjs.verifySignature = function (hash, sig, pubkey) {
|
|
|
|
function parseSig (sig) {
|
|
var cursor;
|
|
if (sig[0] != 0x30)
|
|
throw new Error("Signature not a valid DERSequence");
|
|
|
|
cursor = 2;
|
|
if (sig[cursor] != 0x02)
|
|
throw new Error("First element in signature must be a DERInteger"); ;
|
|
|
|
var rBa = sig.slice(cursor + 2, cursor + 2 + sig[cursor + 1]);
|
|
|
|
cursor += 2 + sig[cursor + 1];
|
|
if (sig[cursor] != 0x02)
|
|
throw new Error("Second element in signature must be a DERInteger");
|
|
|
|
var sBa = sig.slice(cursor + 2, cursor + 2 + sig[cursor + 1]);
|
|
|
|
cursor += 2 + sig[cursor + 1];
|
|
|
|
var r = BigInteger.fromByteArrayUnsigned(rBa);
|
|
var s = BigInteger.fromByteArrayUnsigned(sBa);
|
|
|
|
return { r: r, s: s };
|
|
}
|
|
|
|
var r, s;
|
|
|
|
if (coinjs.isArray(sig)) {
|
|
var obj = parseSig(sig);
|
|
r = obj.r;
|
|
s = obj.s;
|
|
} else if ("object" === typeof sig && sig.r && sig.s) {
|
|
r = sig.r;
|
|
s = sig.s;
|
|
} else {
|
|
throw "Invalid value for signature";
|
|
}
|
|
|
|
var Q;
|
|
if (coinjs.isArray(pubkey)) {
|
|
var ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
|
Q = EllipticCurve.PointFp.decodeFrom(ecparams.getCurve(), pubkey);
|
|
} else {
|
|
throw "Invalid format for pubkey value, must be byte array";
|
|
}
|
|
var e = BigInteger.fromByteArrayUnsigned(hash);
|
|
|
|
return coinjs.verifySignatureRaw(e, r, s, Q);
|
|
}
|
|
|
|
coinjs.verifySignatureRaw = function (e, r, s, Q) {
|
|
var ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
|
var n = ecparams.getN();
|
|
var G = ecparams.getG();
|
|
|
|
if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(n) >= 0)
|
|
return false;
|
|
|
|
if (s.compareTo(BigInteger.ONE) < 0 || s.compareTo(n) >= 0)
|
|
return false;
|
|
|
|
var c = s.modInverse(n);
|
|
|
|
var u1 = e.multiply(c).mod(n);
|
|
var u2 = r.multiply(c).mod(n);
|
|
|
|
var point = G.multiply(u1).add(Q.multiply(u2));
|
|
|
|
var v = point.getX().toBigInteger().mod(n);
|
|
|
|
return v.equals(r);
|
|
}
|
|
|
|
/* start of privates functions */
|
|
|
|
/* base58 encode function */
|
|
coinjs.base58encode = function(buffer) {
|
|
var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
var base = BigInteger.valueOf(58);
|
|
|
|
var bi = BigInteger.fromByteArrayUnsigned(buffer);
|
|
var chars = [];
|
|
|
|
while (bi.compareTo(base) >= 0) {
|
|
var mod = bi.mod(base);
|
|
chars.unshift(alphabet[mod.intValue()]);
|
|
bi = bi.subtract(mod).divide(base);
|
|
}
|
|
|
|
chars.unshift(alphabet[bi.intValue()]);
|
|
for (var i = 0; i < buffer.length; i++) {
|
|
if (buffer[i] == 0x00) {
|
|
chars.unshift(alphabet[0]);
|
|
} else break;
|
|
}
|
|
return chars.join('');
|
|
}
|
|
|
|
/* base58 decode function */
|
|
coinjs.base58decode = function(buffer){
|
|
var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
var base = BigInteger.valueOf(58);
|
|
var validRegex = /^[1-9A-HJ-NP-Za-km-z]+$/;
|
|
|
|
var bi = BigInteger.valueOf(0);
|
|
var leadingZerosNum = 0;
|
|
for (var i = buffer.length - 1; i >= 0; i--) {
|
|
var alphaIndex = alphabet.indexOf(buffer[i]);
|
|
if (alphaIndex < 0) {
|
|
throw "Invalid character";
|
|
}
|
|
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(base.pow(buffer.length - 1 - i)));
|
|
|
|
if (buffer[i] == "1") leadingZerosNum++;
|
|
else leadingZerosNum = 0;
|
|
}
|
|
|
|
var bytes = bi.toByteArrayUnsigned();
|
|
while (leadingZerosNum-- > 0) bytes.unshift(0);
|
|
return bytes;
|
|
}
|
|
|
|
/* raw ajax function to avoid needing bigger frame works like jquery, mootools etc */
|
|
coinjs.ajax = function(u, f, m, a){
|
|
var x = false;
|
|
try{
|
|
x = new ActiveXObject('Msxml2.XMLHTTP')
|
|
} catch(e) {
|
|
try {
|
|
x = new ActiveXObject('Microsoft.XMLHTTP')
|
|
} catch(e) {
|
|
x = new XMLHttpRequest()
|
|
}
|
|
}
|
|
|
|
if(x==false) {
|
|
return false;
|
|
}
|
|
|
|
x.open(m, u, true);
|
|
x.onreadystatechange=function(){
|
|
if((x.readyState==4) && f)
|
|
f(x.responseText);
|
|
};
|
|
|
|
if(m == 'POST'){
|
|
x.setRequestHeader('Content-type','application/x-www-form-urlencoded');
|
|
}
|
|
|
|
x.send(a);
|
|
}
|
|
|
|
/* clone an object */
|
|
coinjs.clone = function(obj) {
|
|
if(obj == null || typeof(obj) != 'object') return obj;
|
|
var temp = obj.constructor();
|
|
|
|
for(var key in obj) {
|
|
if(obj.hasOwnProperty(key)) {
|
|
temp[key] = coinjs.clone(obj[key]);
|
|
}
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
coinjs.numToBytes = function(num,bytes) {
|
|
if (typeof bytes === undefined) bytes = 8;
|
|
if (bytes == 0) {
|
|
return [];
|
|
} else {
|
|
return [num % 256].concat(coinjs.numToBytes(Math.floor(num / 256),bytes-1));
|
|
}
|
|
}
|
|
|
|
coinjs.numToVarInt = function(num) {
|
|
if (num < 253) {
|
|
return [num];
|
|
} else if (num < 65536) {
|
|
return [253].concat(coinjs.numToBytes(num,2));
|
|
} else if (num < 4294967296) {
|
|
return [254].concat(coinjs.numToBytes(num,4));
|
|
} else {
|
|
return [253].concat(coinjs.numToBytes(num,8));
|
|
}
|
|
}
|
|
|
|
coinjs.bytesToNum = function(bytes) {
|
|
if (bytes.length == 0) return 0;
|
|
else return bytes[0] + 256 * coinjs.bytesToNum(bytes.slice(1));
|
|
}
|
|
|
|
coinjs.isArray = function(o){
|
|
return Object.prototype.toString.call(o) === '[object Array]';
|
|
}
|
|
|
|
coinjs.countObject = function(obj){
|
|
var count = 0;
|
|
var i;
|
|
for (i in obj) {
|
|
if (obj.hasOwnProperty(i)) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
coinjs.random = function(length) {
|
|
var r = "";
|
|
var l = length || 25;
|
|
var chars = "!$%^&*()_+{}:@~?><|\./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
for(x=0;x<l;x++) {
|
|
r += chars.charAt(Math.floor(Math.random() * 62));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
})();
|