/* 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 = '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 randArr = new Uint8Array(32); window.crypto.getRandomValues(randArr); var privateKeyBytes = []; for (var i = 0; i < randArr.length; ++i){ privateKeyBytes[i] = randArr[i]; } return Crypto.util.bytesToHex(privateKeyBytes); } /* 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, 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>> 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, 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 = {}; var unspent = data.getElementsByTagName("unspent")[0]; for(i=1;i<=unspent.childElementCount;i++){ var u = data.getElementsByTagName("unspent_"+i)[0] var txhash = u.getElementsByTagName("tx_hash")[0].childNodes[0].nodeValue; var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue; value += u.getElementsByTagName("value")[0].childNodes[0].nodeValue*1; total++; } x.unspent = $(data).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, 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= 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; } })();