diff --git a/LICENCE b/LICENSE similarity index 100% rename from LICENCE rename to LICENSE diff --git a/README.md b/README.md index 209dad5..393fb62 100644 --- a/README.md +++ b/README.md @@ -25,5 +25,6 @@ Coinb.in supports a number of key features such as: - Compatible with bitcoin-qt - An offical .onion address for tor users. - Offline qrcode creator and scanning tool +- HD (bip32) support Donate to 1CWHWkTWaq1K5hevimJia3cyinQsrgXUvg to see more development! diff --git a/index.html b/index.html index a0a8213..efa7018 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ + @@ -53,6 +54,7 @@
@@ -90,7 +92,7 @@Coinbin is an open source web based wallet written in javascript and released under the MIT licence which means its free to use and edit.
+Coinbin is an open source web based wallet written in javascript and released under the MIT license which means its free to use and edit.
Use the form below to generate a master hierarchical deterministic address.
+ + +You can use the advanced options below to generate different kind of master address.
+ +Use this page to create a raw transaction
@@ -518,7 +551,7 @@Enter the raw transaction, redeem script, pubkey or wif key to convert it into a readable format that can be verified manually.
+Enter the raw transaction, redeem script, pubkey, hd address or wif key to convert it into a readable format that can be verified manually.
Address:
The path of key derivation
+ +Keys derived from the hd address provided
+ +Index | Address | Private Key (WIF) | Extended xPub | Extended xPrv |
Github https://github.com/OutCast3k/coinbin/
Coinb.in is a free and open source project released under the MIT licence, originally by OutCast3k in 2013. Discussion of the project can be found at bitcointalk.org during its early testing stages when its primary focus was to develop a proof of concept multisig solution in javascript.
+Coinb.in is a free and open source project released under the MIT license, originally by OutCast3k in 2013. Discussion of the project can be found at bitcointalk.org during its early testing stages when its primary focus was to develop a proof of concept multisig solution in javascript.
Coinb.in is run and funded by the generosity of others in terms of development and hosting.
Coinb.in beleives strongly in privacy, not only do we support the use of TOR, the site does not collect and store IP or transaction data via our servers nor do we store your bitcoins private key. We do use google analytics to track hits and route traffic via cloudflare using an SSL certificate.
diff --git a/js/coin.js b/js/coin.js index ba3527f..73e108d 100644 --- a/js/coin.js +++ b/js/coin.js @@ -1,6 +1,6 @@ /* Coinjs 0.01 beta by OutCast3k{at}gmail.com - A bitcoin framework loosely based on bitcoinjs. + A bitcoin frameworkcoinjs. http://github.com/OutCast3k/coinjs or http://coinb.in/coinjs */ @@ -13,10 +13,12 @@ coinjs.pub = 0x00; coinjs.priv = 0x80; coinjs.multisig = 0x05; + coinjs.hdkey = {'prv':0x0488ade4, 'pub':0x0488b21e}; + coinjs.compressed = false; /* other vars */ - coinjs.developer = '1CWHWkTWaq1K5hevimJia3cyinQsrgXUvg'; + coinjs.developer = '1CWHWkTWaq1K5hevimJia3cyinQsrgXUvg'; // bitcoin /* bit(coinb.in) api vars */ coinjs.host = ('https:'==document.location.protocol?'https://':'http://')+'coinb.in/api/'; @@ -26,8 +28,8 @@ /* 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(); + coinjs.newKeys = function(input){ + var privkey = (input) ? Crypto.SHA256(input) : this.newPrivkey(); var pubkey = this.newPubkey(privkey); return { 'privkey': privkey, @@ -376,9 +378,199 @@ return result_txt; }; + /* start of hd functions, thanks bip32.org */ + coinjs.hd = function(data){ + + var r = {}; + + /* some hd value parsing */ + r.parse = function() { + + var bytes = []; + + // some quick validation + if(typeof(data) == 'string'){ + var decoded = coinjs.base58decode(data); + if(decoded.length == 82){ + var checksum = decoded.slice(78, 82); + var hash = Crypto.SHA256(Crypto.SHA256(decoded.slice(0, 78), { asBytes: true } ), { asBytes: true } ); + if(checksum[0]==hash[0] && checksum[1]==hash[1] && checksum[2]==hash[2] && checksum[3]==hash[3]){ + bytes = decoded.slice(0, 78); + } + } + } + + // actual parsing code + if(bytes && bytes.length>0) { + r.version = coinjs.uint(bytes.slice(0, 4) , 4); + r.depth = coinjs.uint(bytes.slice(4, 5) ,1); + r.parent_fingerprint = bytes.slice(5, 9); + r.child_index = coinjs.uint(bytes.slice(9, 13), 4); + r.chain_code = bytes.slice(13, 45); + r.key_bytes = bytes.slice(45, 78); + + var c = coinjs.compressed; // get current default + coinjs.compressed = true; + + if(r.key_bytes[0] == 0x00) { + r.type = 'private'; + var privkey = (r.key_bytes).slice(1, 33); + var privkeyHex = Crypto.util.bytesToHex(privkey); + var pubkey = coinjs.newPubkey(privkeyHex); + + r.keys = {'privkey':privkeyHex, + 'pubkey':pubkey, + 'address':coinjs.pubkey2address(pubkey), + 'wif':coinjs.privkey2wif(privkeyHex)}; + + } else if(r.key_bytes[0] == 0x02 || r.key_bytes[0] == 0x03) { + r.type = 'public'; + var pubkeyHex = Crypto.util.bytesToHex(r.key_bytes); + + r.keys = {'pubkey': pubkeyHex, + 'address':coinjs.pubkey2address(pubkeyHex)}; + } else { + r.type = 'invalid'; + } + + r.keys_extended = r.extend(); + + coinjs.compressed = c; // reset to default + } + } + + r.extend = function(){ + var hd = coinjs.hd(); + return hd.make({'depth':(this.depth*1)+1, + 'parent_fingerprint':this.parent_fingerprint, + 'child_index':this.child_index, + 'chain_code':this.chain_code, + 'privkey':this.keys.privkey, + 'pubkey':this.keys.pubkey}); + } + + r.derive = function(i){ + i = (i)?i:0; + var blob = (Crypto.util.hexToBytes(this.keys.pubkey)).concat(coinjs.numToBytes(i,4).reverse()); + + var j = new jsSHA(Crypto.util.bytesToHex(blob), 'HEX'); + var hash = j.getHMAC(Crypto.util.bytesToHex(r.chain_code), "HEX", "SHA-512", "HEX"); + + var il = new BigInteger(hash.slice(0, 64), 16); + var ir = Crypto.util.hexToBytes(hash.slice(64,128)); + + var ecparams = EllipticCurve.getSECCurveByName("secp256k1"); + var curve = ecparams.getCurve(); + + var k, key, pubkey, o; + + o = coinjs.clone(this); + o.chain_code = ir; + o.child_index = i; + + if(this.type=='private'){ + k = il.add(new BigInteger([0].concat(Crypto.util.hexToBytes(this.keys.privkey)))).mod(ecparams.getN()); + key = Crypto.util.bytesToHex(k.toByteArrayUnsigned()); + + pubkey = coinjs.newPubkey(key); + + o.keys = {'privkey':key, + 'pubkey':pubkey, + 'wif':coinjs.privkey2wif(key), + 'address':coinjs.pubkey2address(pubkey)}; + + } else if (this.type=='public'){ + q = ecparams.curve.decodePointHex(this.keys.pubkey); + var curvePt = ecparams.getG().multiply(il).add(q); + + var x = curvePt.getX().toBigInteger(); + var y = curvePt.getY().toBigInteger(); + + var publicKeyBytesCompressed = EllipticCurve.integerToBytes(x,32) + if (y.isEven()){ + publicKeyBytesCompressed.unshift(0x02) + } else { + publicKeyBytesCompressed.unshift(0x03) + } + pubkey = Crypto.util.bytesToHex(publicKeyBytesCompressed); + + o.keys = {'pubkey':pubkey, + 'address':coinjs.pubkey2address(pubkey)} + } else { + // fail + } + + o.parent_fingerprint = (ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(r.keys.pubkey),{asBytes:true}),{asBytes:true})).slice(0,4); + o.keys_extended = o.extend(); + + return o; + } + + r.master = function(pass) { + var seed = (pass) ? Crypto.SHA256(pass) : coinjs.newPrivkey(); + var hasher = new jsSHA(seed, 'HEX'); + var I = hasher.getHMAC("Bitcoin seed", "TEXT", "SHA-512", "HEX"); + + var privkey = Crypto.util.hexToBytes(I.slice(0, 64)); + var chain = Crypto.util.hexToBytes(I.slice(64, 128)); + + var hd = coinjs.hd(); + return hd.make({'depth':0, + 'parent_fingerprint':[0,0,0,0], + 'child_index':0, + 'chain_code':chain, + 'privkey':I.slice(0, 64), + 'pubkey':coinjs.newPubkey(I.slice(0, 64))}); + } + + r.make = function(data){ // { (int) depth, (array) parent_fingerprint, (int) child_index, (byte array) chain_code, (hex str) privkey, (hex str) pubkey} + var k = []; + + //depth + k.push(data.depth*1); + + //parent fingerprint + k = k.concat(data.parent_fingerprint); + + //child index + k = k.concat((coinjs.numToBytes(data.child_index, 4)).reverse()); + + // Chain code + k = k.concat(data.chain_code); + + var o = {}; // results + + // Extended HD Private Key + if(data.privkey){ + var prv = (coinjs.numToBytes(coinjs.hdkey.prv, 4)).reverse(); + prv = prv.concat(k); + prv.push(0x00); + prv = prv.concat(Crypto.util.hexToBytes(data.privkey)); + var hash = Crypto.SHA256( Crypto.SHA256(prv, { asBytes: true } ), { asBytes: true } ); + var checksum = hash.slice(0, 4); + var ret = prv.concat(checksum); + o.privkey = coinjs.base58encode(ret); + } + + // Extended HD Public Key + if(data.pubkey){ + var pub = (coinjs.numToBytes(coinjs.hdkey.pub, 4)).reverse(); + pub = pub.concat(k); + pub = pub.concat(Crypto.util.hexToBytes(data.pubkey)); + var hash = Crypto.SHA256( Crypto.SHA256(pub, { asBytes: true } ), { asBytes: true } ); + var checksum = hash.slice(0, 4); + var ret = pub.concat(checksum); + o.pubkey = coinjs.base58encode(ret); + } + return o; + } + + r.parse(); + return r; + } + /* start of script functions */ - coinjs.script = function(data) { var r = {}; @@ -1211,6 +1403,17 @@ else return bytes[0] + 256 * coinjs.bytesToNum(bytes.slice(1)); } + coinjs.uint = function(f, size) { + if (f.length < size) + throw new Error("not enough data"); + var n = 0; + for (var i = 0; i < size; i++) { + n *= 256; + n += f[i]; + } + return n; + } + coinjs.isArray = function(o){ return Object.prototype.toString.call(o) === '[object Array]'; } diff --git a/js/coinbin.js b/js/coinbin.js index c41bef3..5dcc8b9 100644 --- a/js/coinbin.js +++ b/js/coinbin.js @@ -331,6 +331,32 @@ $(document).ready(function() { } }); + /* new -> Hd address code */ + + $(".deriveHDbtn").click(function(){ + $("#verifyScript").val($("input[type='text']",$(this).parent().parent()).val()); + window.location = "#verify"; + $("#verifyBtn").click(); + }); + + $("#newHDKeysBtn").click(function(){ + coinjs.compressed = true; + var s = ($("#newHDBrainwallet").is(":checked")) ? $("#HDBrainwallet").val() : null; + var hd = coinjs.hd(); + var pair = hd.master(s); + $("#newHDxpub").val(pair.pubkey); + $("#newHDxprv").val(pair.privkey); + + }); + + $("#newHDBrainwallet").click(function(){ + if($(this).is(":checked")){ + $("#HDBrainwallet").removeClass("hidden"); + } else { + $("#HDBrainwallet").addClass("hidden"); + } + }); + /* new -> transaction code */ $("#recipients .addressAddTo").click(function(){ @@ -626,7 +652,9 @@ $(document).ready(function() { if(!decodeTransactionScript()){ if(!decodePrivKey()){ if(!decodePubKey()){ - $("#verifyStatus").removeClass('hidden').fadeOut().fadeIn(); + if(!decodeHDaddress()){ + $("#verifyStatus").removeClass('hidden').fadeOut().fadeIn(); + } } } } @@ -780,6 +808,50 @@ $(document).ready(function() { } } + function decodeHDaddress(){ + var s = $("#verifyScript").val(); + if(s.match(/^x[prv|pub]/)){ + try { + var hd = coinjs.hd(s); + $("#verifyHDaddress .hdKey").html(s); + $("#verifyHDaddress .chain_code").val(Crypto.util.bytesToHex(hd.chain_code)); + $("#verifyHDaddress .depth").val(hd.depth); + $("#verifyHDaddress .version").val('0x'+(hd.version).toString(16)); + $("#verifyHDaddress .child_index").val(hd.child_index); + $("#verifyHDaddress .hdwifkey").val((hd.keys.wif)?hd.keys.wif:''); + $("#verifyHDaddress .key_type").html((((hd.depth==0 && hd.child_index==0)?'Master':'Derived')+' '+hd.type).toLowerCase()); + $("#verifyHDaddress .parent_fingerprint").val(Crypto.util.bytesToHex(hd.parent_fingerprint)); + $("#verifyHDaddress .derived_data table tbody").html(""); + deriveHDaddress(); + $(".verifyLink").attr('href','?verify='+$("#verifyScript").val()); + $("#verifyHDaddress").removeClass("hidden"); + return true; + } catch (e) { + return false; + } + } + } + + function deriveHDaddress() { + var hd = coinjs.hd($("#verifyHDaddress .hdKey").html()); + var index_start = $("#verifyHDaddress .derivation_index_start").val()*1; + var index_end = $("#verifyHDaddress .derivation_index_end").val()*1; + var html = ''; + $("#verifyHDaddress .derived_data table tbody").html(""); + for(var i=index_start;i<=index_end;i++){ + var derived = hd.derive(i); + html += '