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;i++){
|
|
r = Crypto.SHA256(r);
|
|
}
|
|
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 = 4294967295;
|
|
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.locktime = 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 (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;
|
|
}
|
|
|
|
})();
|