Skip to content

Commit

Permalink
first stage of adding bech32 support added to coinb.in
Browse files Browse the repository at this point in the history
  • Loading branch information
OutCast3k committed May 27, 2018
1 parent abd2191 commit 90a309d
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 39 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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!
27 changes: 22 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ <h3><span class="glyphicon glyphicon-piggy-bank"></span> Wallet</h3>

<div class="col-md-4">
<h3><span class="glyphicon glyphicon-globe"></span> Addresses</h3>
<p>We support <a href="#newAddress">regular addresses</a>, <a href="#newMultiSig">multisig</a>, <a href="#newSegWit">segwit</a> and stealth all with access to your own private keys!</p>
<p>We support <a href="#newAddress">regular addresses</a>, <a href="#newMultiSig">multisig</a>, <a href="#newSegWit">segwit / bech32</a> and stealth all with access to your own private keys!</p>
</div>

<div class="col-md-4">
Expand Down Expand Up @@ -371,6 +371,10 @@ <h2>New SegWit Address <small> Smaller &amp; Faster Transactions</small></h2>
<h3>Address Options</h3>
<p>You can use the advanced options below to generate different kind of keys and addresses.</p>

<div class="checkbox">
<label><input type="checkbox" id="newSegWitBech32addr" class="checkbox-inline" checked> Enable <a href="https://en.bitcoin.it/wiki/Bech32" target="_blank">Bech32</a>?</label>
</div>

<div class="checkbox">
<label><input type="checkbox" id="newSegWitBrainwallet" class="checkbox-inline"> Custom Seed or Brain Wallet</label>
<input type="text" class="form-control hidden" id="brainwalletSegWit">
Expand Down Expand Up @@ -1090,13 +1094,26 @@ <h4>Public key</h4>
<hr>
<div class="row">
<div class="col-md-6">
<p><b>Segwit Address</b>: <input type="text" class="form-control addressSegWit" readonly></p>
<p><b>P2SH Segwit Address</b>: <input type="text" class="form-control addressSegWit" readonly></p>
</div>

<div class="col-md-6">
<p><b>P2SH Segwit Redeem Script</b>: <input type="text" class="form-control addressSegWitRedeemScript" readonly></p>
</div>
</div>

<hr>
<div class="row">
<div class="col-md-6">
<p><b>Bech32 Address</b>: <input type="text" class="form-control addressBech32" readonly></p>
</div>

<div class="col-md-6">
<p><b>Segwit Redeem Script</b>: <input type="text" class="form-control addressSegWitRedeemScript" readonly></p>
<p><b>Bech32 Redeem Script</b>: <input type="text" class="form-control addressBech32RedeemScript" readonly></p>
</div>
</div>

<br>
</div>
</div>

Expand Down Expand Up @@ -1286,7 +1303,7 @@ <h2>Development <small>Javascript framework, API and more</small></h2>

<div class="tab-pane tab-content" id="about">
<h2>About <small>open source bitcoin wallet</small></h2>
<p>Version 1.3</p>
<p>Version 1.4</p>
<p>Compatible with bitcoin core</p>
<p>Github <a href="https://github.com/OutCast3k/coinbin/">https://github.com/OutCast3k/coinbin/</a></p>
<p>TOR <a href="http://4zpinp6gdkjfplhk.onion">4zpinp6gdkjfplhk.onion</a></p>
Expand Down Expand Up @@ -1413,7 +1430,7 @@ <h2>Settings <small> making coinb.in even better!</small></h2>

<div id="footer">
<div class="container text-right">
<p class="text-muted">Version 1.3</p>
<p class="text-muted">Version 1.4</p>
</div>
</div>

Expand Down
197 changes: 183 additions & 14 deletions js/coin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -286,7 +305,12 @@
return false;
}
} catch(e) {
return false;
bech32rs = coinjs.bech32redeemscript(addr);
if(bech32rs){
return {'type':'bech32', 'redeemscript':bech32rs};
} else {
return false;
}
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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])};
Expand Down Expand Up @@ -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]);
Expand All @@ -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;
}
}
Expand Down
Loading

0 comments on commit 90a309d

Please sign in to comment.