From 90a309d4b46d69aaff222bc217f369c52dfed146 Mon Sep 17 00:00:00 2001 From: OutCast3k Date: Sun, 27 May 2018 11:49:09 +0000 Subject: [PATCH] first stage of adding bech32 support added to coinb.in --- README.md | 3 +- index.html | 27 +++++-- js/coin.js | 197 ++++++++++++++++++++++++++++++++++++++++++++++---- js/coinbin.js | 47 ++++++++---- sha1sum | 10 +-- 5 files changed, 245 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 23cb7e8..2b6c31f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ coinbin ======= -A Open Source Browser Based Bitcoin Wallet. Version 1.3 beta by OutCast3k +A Open Source Browser Based Bitcoin Wallet. Version 1.4 beta by OutCast3k Live version available at http://coinb.in/ or http://4zpinp6gdkjfplhk.onion @@ -29,6 +29,7 @@ Coinb.in supports a number of key features such as: - Supports altcoins such as litecoin - Replace by fee (RBF) Support - Segwit Support +- Bech32 address support - Fee calculator - https://coinb.in/#fees Donate to 3K1oFZMks41C7qDYBsr72SYjapLqDuSYuN to see more development! diff --git a/index.html b/index.html index 89f6abe..f95a004 100644 --- a/index.html +++ b/index.html @@ -125,7 +125,7 @@

Addresses

-

We support regular addresses, multisig, segwit and stealth all with access to your own private keys!

+

We support regular addresses, multisig, segwit / bech32 and stealth all with access to your own private keys!

@@ -371,6 +371,10 @@

Address Options

You can use the advanced options below to generate different kind of keys and addresses.

+
+ +
+
@@ -1090,13 +1094,26 @@
-

Segwit Address:

+

P2SH Segwit Address:

-

Segwit Redeem Script:

+

P2SH Segwit Redeem Script:

+ +
+
+
+

Bech32 Address:

+
+ +
+

Bech32 Redeem Script:

+
+
+ +
@@ -1286,7 +1303,7 @@

About open source bitcoin wallet

-

Version 1.3

+

Version 1.4

Compatible with bitcoin core

Github https://github.com/OutCast3k/coinbin/

TOR 4zpinp6gdkjfplhk.onion

@@ -1413,7 +1430,7 @@ diff --git a/js/coin.js b/js/coin.js index 33cc88e..02ca1c5 100644 --- a/js/coin.js +++ b/js/coin.js @@ -14,6 +14,7 @@ coinjs.priv = 0x80; coinjs.multisig = 0x05; coinjs.hdkey = {'prv':0x0488ade4, 'pub':0x0488b21e}; + coinjs.bech32 = {'charset':'qpzry9x8gf2tvdw0s3jn54khce6mua7l', 'version':0, 'hrp':'bc'}; coinjs.compressed = false; @@ -184,6 +185,24 @@ return {'address':address, 'type':'segwit', 'redeemscript':Crypto.util.bytesToHex(keyhash)}; } + /* create a new segwit bech32 encoded address */ + coinjs.bech32Address = function(pubkey){ + var program = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubkey), {asBytes: true}), {asBytes: true}); + var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true))); + return {'address':address, 'type':'bech32', 'redeemscript':Crypto.util.bytesToHex(program)}; + } + + /* extract the redeemscript from a bech32 address */ + coinjs.bech32redeemscript = function(address){ + var r = false; + var decode = coinjs.bech32_decode(address); + if(decode){ + decode.data.shift(); + return Crypto.util.bytesToHex(coinjs.bech32_convert(decode.data, 5, 8, true)); + } + return r; + } + /* provide a privkey and return an WIF */ coinjs.privkey2wif = function(h){ var r = Crypto.util.hexToBytes(h); @@ -286,7 +305,12 @@ return false; } } catch(e) { - return false; + bech32rs = coinjs.bech32redeemscript(addr); + if(bech32rs){ + return {'type':'bech32', 'redeemscript':bech32rs}; + } else { + return false; + } } } @@ -316,6 +340,126 @@ return false; } + coinjs.bech32_polymod = function(values) { + var chk = 1; + var BECH32_GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + for (var p = 0; p < values.length; ++p) { + var top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[p]; + for (var i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= BECH32_GENERATOR[i]; + } + } + } + return chk; + } + + coinjs.bech32_hrpExpand = function(hrp) { + var ret = []; + var p; + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5); + } + ret.push(0); + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return ret; + } + + coinjs. bech32_verifyChecksum = function(hrp, data) { + return coinjs.bech32_polymod(coinjs.bech32_hrpExpand(hrp).concat(data)) === 1; + } + + coinjs.bech32_createChecksum = function(hrp, data) { + var values = coinjs.bech32_hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + var mod = coinjs.bech32_polymod(values) ^ 1; + var ret = []; + for (var p = 0; p < 6; ++p) { + ret.push((mod >> 5 * (5 - p)) & 31); + } + return ret; + } + + coinjs.bech32_encode = function(hrp, data) { + var combined = data.concat(coinjs.bech32_createChecksum(hrp, data)); + var ret = hrp + '1'; + for (var p = 0; p < combined.length; ++p) { + ret += coinjs.bech32.charset.charAt(combined[p]); + } + return ret; + } + + coinjs.bech32_decode = function(bechString) { + var p; + var has_lower = false; + var has_upper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + has_lower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + has_upper = true; + } + } + if (has_lower && has_upper) { + return null; + } + bechString = bechString.toLowerCase(); + var pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + var hrp = bechString.substring(0, pos); + var data = []; + for (p = pos + 1; p < bechString.length; ++p) { + var d = coinjs.bech32.charset.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + if (!coinjs.bech32_verifyChecksum(hrp, data)) { + return null; + } + return { + hrp: hrp, + data: data.slice(0, data.length - 6) + }; + } + + coinjs.bech32_convert = function(data, inBits, outBits, pad) { + var value = 0; + var bits = 0; + var maxV = (1 << outBits) - 1; + + var result = []; + for (var i = 0; i < data.length; ++i) { + value = (value << inBits) | data[i]; + bits += inBits; + + while (bits >= outBits) { + bits -= outBits; + result.push((value >> bits) & maxV); + } + } + + if (pad) { + if (bits > 0) { + result.push((value << (outBits - bits)) & maxV); + } + } else { + if (bits >= inBits) throw new Error('Excess padding'); + if ((value << (outBits - bits)) & maxV) throw new Error('Non-zero padding'); + } + + return result; + } + coinjs.testdeterministicK = function() { // https://github.com/bitpay/bitcore/blob/9a5193d8e94b0bd5b8e7f00038e7c0b935405a03/test/crypto/ecdsa.js // Line 21 and 22 specify digest hash and privkey for the first 2 test vectors. @@ -728,7 +872,10 @@ r.spendToScript = function(address){ var addr = coinjs.addressDecode(address); var s = coinjs.script(); - if(addr.version==coinjs.multisig){ // multisig address + if(addr.type == "bech32"){ + s.writeOp(0); + s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript)); + } else if(addr.version==coinjs.multisig){ // multisig address s.writeOp(169); //OP_HASH160 s.writeBytes(addr.bytes); s.writeOp(135); //OP_EQUAL @@ -1043,20 +1190,24 @@ return {'result':0, 'fail':'redeemscript', 'response':'redeemscript missing or not valid for segwit'}; } - var scriptcode = Crypto.util.hexToBytes(extract['script']); - if(scriptcode[0] != 0){ - return {'result':0, 'fail':'scriptcode', 'response':'redeemscript is not valid'}; - } - if(extract['value'] == -1){ return {'result':0, 'fail':'value', 'response':'unable to generate a valid segwit hash without a value'}; } + var scriptcode = Crypto.util.hexToBytes(extract['script']); + // end of redeem script check - scriptcode = scriptcode.slice(1); - scriptcode.unshift(25, 118, 169); - scriptcode.push(136, 172); + /* P2WPKH */ + if(scriptcode.length == 20){ + scriptcode = [0x00,0x14].concat(scriptcode); + } + + if(scriptcode.length == 22){ + scriptcode = scriptcode.slice(1); + scriptcode.unshift(25, 118, 169); + scriptcode.push(136, 172); + } var value = coinjs.numToBytes(extract['value'], 8); @@ -1137,7 +1288,17 @@ } else if(this.ins[index].script.chunks.length == 5 && this.ins[index].script.chunks[1] == 177){//OP_CHECKLOCKTIMEVERIFY // hodl script (not signed) return {'type':'hodl', 'signed':'false', 'signatures': 0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)}; - } else if((this.ins[index].script.chunks.length <= 3 && this.ins[index].script.chunks.length > 0) && this.ins[index].script.chunks[0].length == 22 && this.ins[index].script.chunks[0][0] == 0){ + } else if((this.ins[index].script.chunks.length <= 3 && this.ins[index].script.chunks.length > 0) && ((this.ins[index].script.chunks[0].length == 22 && this.ins[index].script.chunks[0][0] == 0) || (this.ins[index].script.chunks[0].length == 20 && this.ins[index].script.chunks[1] == 0))){ + var signed = ((this.witness[index]) && this.witness[index].length==2) ? 'true' : 'false'; + var sigs = (signed == 'true') ? 1 : 0; + var value = -1; // no value found + if((this.ins[index].script.chunks[2]) && this.ins[index].script.chunks[2].length==8){ + value = coinjs.bytesToNum(this.ins[index].script.chunks[2]); // value found encoded in transaction (THIS IS NON STANDARD) + } + return {'type':'segwit', 'signed':signed, 'signatures': sigs, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), 'value': value}; + + /* } else if((this.ins[index].script.chunks.length <= 3 && this.ins[index].script.chunks.length > 0) && (this.ins[index].script.chunks[0].length == 22 && this.ins[index].script.chunks[0][0] == 0)){ + alert('p2sh'); // segwit script var signed = ((this.witness[index]) && this.witness[index].length==2) ? 'true' : 'false'; var sigs = (signed == 'true') ? 1 : 0; @@ -1146,6 +1307,7 @@ value = coinjs.bytesToNum(this.ins[index].script.chunks[2]); // value found encoded in transaction (THIS IS NON STANDARD) } return {'type':'segwit', 'signed':signed, 'signatures': sigs, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), 'value': value}; + */ } 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])}; @@ -1367,13 +1529,16 @@ var wif2 = coinjs.wif2pubkey(wif); var segwit = coinjs.segwitAddress(wif2['pubkey']); + var bech32 = coinjs.bech32Address(wif2['pubkey']); - if(segwit['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])){ + if((segwit['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])) || (bech32['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0]))){ var txhash = this.transactionHashSegWitV0(index, shType); + if(txhash.result == 1){ + var segwitHash = Crypto.util.hexToBytes(txhash.hash); var signature = this.transactionSig(index, wif, shType, segwitHash); - + // remove any non standard data we store, i.e. input value var script = coinjs.script(); script.writeBytes(this.ins[index].script.chunks[0]); @@ -1396,9 +1561,13 @@ for(var y = 0; y < this.witness.length; y++){ if(!witness_used.includes(y)){ var sw = coinjs.segwitAddress(this.witness[y][1]); - if(sw['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])){ + var b32 = coinjs.bech32Address(this.witness[y][1]); + if((sw['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])) || (b32['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0]))){ witness_order.push(this.witness[y]); witness_used.push(y); + if(b32['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])){ + this.ins[index].script = coinjs.script(); + } break; } } diff --git a/js/coinbin.js b/js/coinbin.js index efeb75d..7db57ac 100644 --- a/js/coinbin.js +++ b/js/coinbin.js @@ -170,6 +170,7 @@ $(document).ready(function() { var signed = txunspent.sign($("#walletKeys .privkey").val()); // and finally broadcast! + tx2.broadcast(function(data){ if($(data).find("result").text()=="1"){ $("#walletSendConfirmStatus").removeClass('hidden').addClass('alert-success').html('txid: '+$(data).find("txid").text()+''); @@ -349,9 +350,16 @@ $(document).ready(function() { $("#newSegWitKeysBtn").click(function(){ var compressed = coinjs.compressed; coinjs.compressed = true; + var s = ($("#newSegWitBrainwallet").is(":checked")) ? $("#brainwalletSegWit").val() : null; var coin = coinjs.newKeys(s); - var sw = coinjs.segwitAddress(coin.pubkey); + + if($("#newSegWitBech32addr").is(":checked")){ + var sw = coinjs.bech32Address(coin.pubkey); + } else { + var sw = coinjs.segwitAddress(coin.pubkey); + } + $("#newSegWitAddress").val(sw.address); $("#newSegWitRedeemScript").val(sw.redeemscript); $("#newSegWitPubKey").val(coin.pubkey); @@ -621,7 +629,7 @@ $(document).ready(function() { $.each($("#recipients .row"), function(i,o){ var a = ($(".address",o).val()); var ad = coinjs.addressDecode(a); - if(((a!="") && (ad.version == coinjs.pub || ad.version == coinjs.multisig)) && $(".amount",o).val()!=""){ // address + if(((a!="") && (ad.version == coinjs.pub || ad.version == coinjs.multisig || ad.type=="bech32")) && $(".amount",o).val()!=""){ // address // P2SH output is 32, P2PKH is 34 estimatedTxSize += (ad.version == coinjs.pub ? 34 : 32) tx.addoutput(a, $(".amount",o).val()); @@ -831,7 +839,7 @@ $(document).ready(function() { } if(redeem.from=='other'){ - $("#redeemFromStatus").removeClass('hidden').html(' The address or multisig redeem script you have entered is invalid'); + $("#redeemFromStatus").removeClass('hidden').html(' The address or redeem script you have entered is invalid'); return false; } @@ -871,28 +879,33 @@ $(document).ready(function() { if(decode.version == coinjs.pub){ // regular address r.addr = string; r.from = 'address'; - r.isMultisig = false; + r.redeemscript = false; } else if (decode.version == coinjs.priv){ // wif key var a = coinjs.wif2address(string); r.addr = a['address']; r.from = 'wif'; - r.isMultisig = false; + r.redeemscript = false; } else if (decode.version == coinjs.multisig){ // mulisig address r.addr = ''; r.from = 'multisigAddress'; - r.isMultisig = false; + r.redeemscript = false; + } else if(decode.type == 'bech32'){ + r.addr = string; + r.from = 'bech32'; + r.decodedRs = decode.redeemscript; + r.redeemscript = true; } else { var script = coinjs.script(); var decodeRs = script.decodeRedeemScript(string); if(decodeRs){ // redeem script r.addr = decodeRs['address']; r.from = 'redeemScript'; - r.decodedRs = decodeRs; - r.isMultisig = true; // not quite, may be hodl + r.decodedRs = decodeRs.redeemscript; + r.redeemscript = true; } else { // something else r.addr = ''; r.from = 'other'; - r.isMultisig = false; + r.redeemscript = false; } } return r; @@ -947,7 +960,7 @@ $(document).ready(function() { $("#inputs .txIdN:last").val(n); $("#inputs .txIdAmount:last").val(amount); - if(script.match(/^00/) && script.length==44){ + if(((script.match(/^00/) && script.length==44)) || (script.length==40 && script.match(/^[a-f0-9]+$/gi))){ s = coinjs.script(); s.writeBytes(Crypto.util.hexToBytes(script)); s.writeOp(0); @@ -969,7 +982,7 @@ $(document).ready(function() { $.each($(data).find("unspent").children(), function(i,o){ var tx = $(o).find("tx_hash").text(); var n = $(o).find("tx_output_n").text(); - var script = (redeem.isMultisig==true) ? $("#redeemFrom").val() : $(o).find("script").text(); + var script = (redeem.redeemscript==true) ? redeem.decodedRs : $(o).find("script").text(); var amount = (($(o).find("value").text()*1)/100000000).toFixed(8); addOutput(tx, n, script, amount); @@ -1000,7 +1013,7 @@ $(document).ready(function() { var o = data.data.txs[i]; var tx = ((o.txid).match(/.{1,2}/g).reverse()).join("")+''; var n = o.output_no; - var script = (redeem.isMultisig==true) ? $("#redeemFrom").val() : o.script_hex; + var script = (redeem.redeemscript==true) ? redeem.decodedRs : o.script_hex; var amount = o.value; addOutput(tx, n, script, amount); } @@ -1033,7 +1046,7 @@ $(document).ready(function() { $.each($(data).find("unspent").children(), function(i,o){ var tx = $(o).find("tx_hash").text(); var n = $(o).find("tx_output_n").text(); - var script = (redeem.isMultisig==true) ? $("#redeemFrom").val() : o.script_hex; + var script = (redeem.redeemscript==true) ? redeem.decodedRs : o.script_hex; var amount = (($(o).find("value").text()*1)/100000000).toFixed(8); addOutput(tx, n, script, amount); }); @@ -1067,7 +1080,7 @@ $(document).ready(function() { var tx = ((""+o.txid).match(/.{1,2}/g).reverse()).join("")+''; if(tx.match(/^[a-f0-9]+$/)){ var n = o.output_no; - var script = (redeem.isMultisig==true) ? $("#redeemFrom").val() : o.script_hex; + var script = (redeem.redeemscript==true) ? redeem.decodedRs : o.script_hex; var amount = o.value; addOutput(tx, n, script, amount); } @@ -1418,6 +1431,8 @@ $(document).ready(function() { var addr = ''; if(o.script.chunks.length==5){ addr = coinjs.scripthash2address(Crypto.util.bytesToHex(o.script.chunks[2])); + } else if((o.script.chunks.length==2) && o.script.chunks[0]==0){ + addr = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(o.script.chunks[1], 8, 5, true))); } else { var pub = coinjs.pub; coinjs.pub = coinjs.multisig; @@ -1482,6 +1497,10 @@ $(document).ready(function() { $("#verifyPubKey .addressSegWit").val(sw.address); $("#verifyPubKey .addressSegWitRedeemScript").val(sw.redeemscript); + var b32 = coinjs.bech32Address(pubkey); + $("#verifyPubKey .addressBech32").val(b32.address); + $("#verifyPubKey .addressBech32RedeemScript").val(b32.redeemscript); + $("#verifyPubKey .verifyDataSw").removeClass('hidden'); } $("#verifyPubKey").removeClass("hidden"); diff --git a/sha1sum b/sha1sum index 17fcfb2..db1f381 100644 --- a/sha1sum +++ b/sha1sum @@ -1,9 +1,9 @@ ----- Version 1.3 2018.01.15 --- +---- Version 1.4 2018.05.27 --- 77e4519962e2f6a9fc93342137dbb31c33b76b04 ./js/aes.js 3a09a8fc0cfe828b57fc798d668234d0490ee1a6 ./js/bootstrap-datetimepicker.min.js 253711c6d825de55a8360552573be950da180614 ./js/bootstrap.min.js -106646cb21653e8eeaa36dbf7ba4c572efbef08c ./js/coinbin.js -2d48b86fe5c7d485c6613afcb0d52d5352ecf01b ./js/coin.js +dda26795fcd22541612067d44cf72ecae62f092b ./js/coinbin.js +d623019884867a13a55832cfb206606ff544e56a ./js/coin.js 988565bc2cb402d63ed5c5fd7ff47c4278efc2c5 ./js/collapse.js 9ba5ede3d7f9d4c8fd623395f196adfdcf7e970f ./js/crypto-min.js f7c09f2f5a721371e7d478050119f7e2d58e3ef9 ./js/crypto-sha256-hmac.js @@ -30,5 +30,5 @@ ca35b697d99cae4d1b60f2d60fcd37771987eb07 ./fonts/glyphicons-halflings-regular.w de51a8494180a6db074af2dee2383f0a363c5b08 ./fonts/glyphicons-halflings-regular.svg 278e49a86e634da6f2a02f3b47dd9d2a8f26210f ./fonts/glyphicons-halflings-regular.woff 44bc1850f570972267b169ae18f1cb06b611ffa2 ./fonts/glyphicons-halflings-regular.ttf -177232f8f3eac41ec9402c581dd26c69e498874d ./README.md -2334df7122e15cae4f045a7e15bc24abbbe79aa7 ./index.html +c024021c71cba503979a859d23cbf7a88b570d82 ./README.md +208b64a1ef61aaceec82f06515e4f7cf046793f6 ./index.html