Skip to content

Commit 90a309d

Browse files
committed
first stage of adding bech32 support added to coinb.in
1 parent abd2191 commit 90a309d

5 files changed

Lines changed: 245 additions & 39 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
coinbin
22
=======
33

4-
A Open Source Browser Based Bitcoin Wallet. Version 1.3 beta by OutCast3k
4+
A Open Source Browser Based Bitcoin Wallet. Version 1.4 beta by OutCast3k
55

66
Live version available at http://coinb.in/ or http://4zpinp6gdkjfplhk.onion
77

@@ -29,6 +29,7 @@ Coinb.in supports a number of key features such as:
2929
- Supports altcoins such as litecoin
3030
- Replace by fee (RBF) Support
3131
- Segwit Support
32+
- Bech32 address support
3233
- Fee calculator - https://coinb.in/#fees
3334

3435
Donate to 3K1oFZMks41C7qDYBsr72SYjapLqDuSYuN to see more development!

index.html

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ <h3><span class="glyphicon glyphicon-piggy-bank"></span> Wallet</h3>
125125

126126
<div class="col-md-4">
127127
<h3><span class="glyphicon glyphicon-globe"></span> Addresses</h3>
128-
<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>
128+
<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>
129129
</div>
130130

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

374+
<div class="checkbox">
375+
<label><input type="checkbox" id="newSegWitBech32addr" class="checkbox-inline" checked> Enable <a href="https://en.bitcoin.it/wiki/Bech32" target="_blank">Bech32</a>?</label>
376+
</div>
377+
374378
<div class="checkbox">
375379
<label><input type="checkbox" id="newSegWitBrainwallet" class="checkbox-inline"> Custom Seed or Brain Wallet</label>
376380
<input type="text" class="form-control hidden" id="brainwalletSegWit">
@@ -1090,13 +1094,26 @@ <h4>Public key</h4>
10901094
<hr>
10911095
<div class="row">
10921096
<div class="col-md-6">
1093-
<p><b>Segwit Address</b>: <input type="text" class="form-control addressSegWit" readonly></p>
1097+
<p><b>P2SH Segwit Address</b>: <input type="text" class="form-control addressSegWit" readonly></p>
1098+
</div>
1099+
1100+
<div class="col-md-6">
1101+
<p><b>P2SH Segwit Redeem Script</b>: <input type="text" class="form-control addressSegWitRedeemScript" readonly></p>
1102+
</div>
1103+
</div>
1104+
1105+
<hr>
1106+
<div class="row">
1107+
<div class="col-md-6">
1108+
<p><b>Bech32 Address</b>: <input type="text" class="form-control addressBech32" readonly></p>
10941109
</div>
10951110

10961111
<div class="col-md-6">
1097-
<p><b>Segwit Redeem Script</b>: <input type="text" class="form-control addressSegWitRedeemScript" readonly></p>
1112+
<p><b>Bech32 Redeem Script</b>: <input type="text" class="form-control addressBech32RedeemScript" readonly></p>
10981113
</div>
10991114
</div>
1115+
1116+
<br>
11001117
</div>
11011118
</div>
11021119

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

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

14141431
<div id="footer">
14151432
<div class="container text-right">
1416-
<p class="text-muted">Version 1.3</p>
1433+
<p class="text-muted">Version 1.4</p>
14171434
</div>
14181435
</div>
14191436

js/coin.js

Lines changed: 183 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
coinjs.priv = 0x80;
1515
coinjs.multisig = 0x05;
1616
coinjs.hdkey = {'prv':0x0488ade4, 'pub':0x0488b21e};
17+
coinjs.bech32 = {'charset':'qpzry9x8gf2tvdw0s3jn54khce6mua7l', 'version':0, 'hrp':'bc'};
1718

1819
coinjs.compressed = false;
1920

@@ -184,6 +185,24 @@
184185
return {'address':address, 'type':'segwit', 'redeemscript':Crypto.util.bytesToHex(keyhash)};
185186
}
186187

188+
/* create a new segwit bech32 encoded address */
189+
coinjs.bech32Address = function(pubkey){
190+
var program = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubkey), {asBytes: true}), {asBytes: true});
191+
var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true)));
192+
return {'address':address, 'type':'bech32', 'redeemscript':Crypto.util.bytesToHex(program)};
193+
}
194+
195+
/* extract the redeemscript from a bech32 address */
196+
coinjs.bech32redeemscript = function(address){
197+
var r = false;
198+
var decode = coinjs.bech32_decode(address);
199+
if(decode){
200+
decode.data.shift();
201+
return Crypto.util.bytesToHex(coinjs.bech32_convert(decode.data, 5, 8, true));
202+
}
203+
return r;
204+
}
205+
187206
/* provide a privkey and return an WIF */
188207
coinjs.privkey2wif = function(h){
189208
var r = Crypto.util.hexToBytes(h);
@@ -286,7 +305,12 @@
286305
return false;
287306
}
288307
} catch(e) {
289-
return false;
308+
bech32rs = coinjs.bech32redeemscript(addr);
309+
if(bech32rs){
310+
return {'type':'bech32', 'redeemscript':bech32rs};
311+
} else {
312+
return false;
313+
}
290314
}
291315
}
292316

@@ -316,6 +340,126 @@
316340
return false;
317341
}
318342

343+
coinjs.bech32_polymod = function(values) {
344+
var chk = 1;
345+
var BECH32_GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
346+
for (var p = 0; p < values.length; ++p) {
347+
var top = chk >> 25;
348+
chk = (chk & 0x1ffffff) << 5 ^ values[p];
349+
for (var i = 0; i < 5; ++i) {
350+
if ((top >> i) & 1) {
351+
chk ^= BECH32_GENERATOR[i];
352+
}
353+
}
354+
}
355+
return chk;
356+
}
357+
358+
coinjs.bech32_hrpExpand = function(hrp) {
359+
var ret = [];
360+
var p;
361+
for (p = 0; p < hrp.length; ++p) {
362+
ret.push(hrp.charCodeAt(p) >> 5);
363+
}
364+
ret.push(0);
365+
for (p = 0; p < hrp.length; ++p) {
366+
ret.push(hrp.charCodeAt(p) & 31);
367+
}
368+
return ret;
369+
}
370+
371+
coinjs. bech32_verifyChecksum = function(hrp, data) {
372+
return coinjs.bech32_polymod(coinjs.bech32_hrpExpand(hrp).concat(data)) === 1;
373+
}
374+
375+
coinjs.bech32_createChecksum = function(hrp, data) {
376+
var values = coinjs.bech32_hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
377+
var mod = coinjs.bech32_polymod(values) ^ 1;
378+
var ret = [];
379+
for (var p = 0; p < 6; ++p) {
380+
ret.push((mod >> 5 * (5 - p)) & 31);
381+
}
382+
return ret;
383+
}
384+
385+
coinjs.bech32_encode = function(hrp, data) {
386+
var combined = data.concat(coinjs.bech32_createChecksum(hrp, data));
387+
var ret = hrp + '1';
388+
for (var p = 0; p < combined.length; ++p) {
389+
ret += coinjs.bech32.charset.charAt(combined[p]);
390+
}
391+
return ret;
392+
}
393+
394+
coinjs.bech32_decode = function(bechString) {
395+
var p;
396+
var has_lower = false;
397+
var has_upper = false;
398+
for (p = 0; p < bechString.length; ++p) {
399+
if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
400+
return null;
401+
}
402+
if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
403+
has_lower = true;
404+
}
405+
if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
406+
has_upper = true;
407+
}
408+
}
409+
if (has_lower && has_upper) {
410+
return null;
411+
}
412+
bechString = bechString.toLowerCase();
413+
var pos = bechString.lastIndexOf('1');
414+
if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
415+
return null;
416+
}
417+
var hrp = bechString.substring(0, pos);
418+
var data = [];
419+
for (p = pos + 1; p < bechString.length; ++p) {
420+
var d = coinjs.bech32.charset.indexOf(bechString.charAt(p));
421+
if (d === -1) {
422+
return null;
423+
}
424+
data.push(d);
425+
}
426+
if (!coinjs.bech32_verifyChecksum(hrp, data)) {
427+
return null;
428+
}
429+
return {
430+
hrp: hrp,
431+
data: data.slice(0, data.length - 6)
432+
};
433+
}
434+
435+
coinjs.bech32_convert = function(data, inBits, outBits, pad) {
436+
var value = 0;
437+
var bits = 0;
438+
var maxV = (1 << outBits) - 1;
439+
440+
var result = [];
441+
for (var i = 0; i < data.length; ++i) {
442+
value = (value << inBits) | data[i];
443+
bits += inBits;
444+
445+
while (bits >= outBits) {
446+
bits -= outBits;
447+
result.push((value >> bits) & maxV);
448+
}
449+
}
450+
451+
if (pad) {
452+
if (bits > 0) {
453+
result.push((value << (outBits - bits)) & maxV);
454+
}
455+
} else {
456+
if (bits >= inBits) throw new Error('Excess padding');
457+
if ((value << (outBits - bits)) & maxV) throw new Error('Non-zero padding');
458+
}
459+
460+
return result;
461+
}
462+
319463
coinjs.testdeterministicK = function() {
320464
// https://github.com/bitpay/bitcore/blob/9a5193d8e94b0bd5b8e7f00038e7c0b935405a03/test/crypto/ecdsa.js
321465
// Line 21 and 22 specify digest hash and privkey for the first 2 test vectors.
@@ -728,7 +872,10 @@
728872
r.spendToScript = function(address){
729873
var addr = coinjs.addressDecode(address);
730874
var s = coinjs.script();
731-
if(addr.version==coinjs.multisig){ // multisig address
875+
if(addr.type == "bech32"){
876+
s.writeOp(0);
877+
s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript));
878+
} else if(addr.version==coinjs.multisig){ // multisig address
732879
s.writeOp(169); //OP_HASH160
733880
s.writeBytes(addr.bytes);
734881
s.writeOp(135); //OP_EQUAL
@@ -1043,20 +1190,24 @@
10431190
return {'result':0, 'fail':'redeemscript', 'response':'redeemscript missing or not valid for segwit'};
10441191
}
10451192

1046-
var scriptcode = Crypto.util.hexToBytes(extract['script']);
1047-
if(scriptcode[0] != 0){
1048-
return {'result':0, 'fail':'scriptcode', 'response':'redeemscript is not valid'};
1049-
}
1050-
10511193
if(extract['value'] == -1){
10521194
return {'result':0, 'fail':'value', 'response':'unable to generate a valid segwit hash without a value'};
10531195
}
10541196

1197+
var scriptcode = Crypto.util.hexToBytes(extract['script']);
1198+
10551199
// end of redeem script check
10561200

1057-
scriptcode = scriptcode.slice(1);
1058-
scriptcode.unshift(25, 118, 169);
1059-
scriptcode.push(136, 172);
1201+
/* P2WPKH */
1202+
if(scriptcode.length == 20){
1203+
scriptcode = [0x00,0x14].concat(scriptcode);
1204+
}
1205+
1206+
if(scriptcode.length == 22){
1207+
scriptcode = scriptcode.slice(1);
1208+
scriptcode.unshift(25, 118, 169);
1209+
scriptcode.push(136, 172);
1210+
}
10601211

10611212
var value = coinjs.numToBytes(extract['value'], 8);
10621213

@@ -1137,7 +1288,17 @@
11371288
} else if(this.ins[index].script.chunks.length == 5 && this.ins[index].script.chunks[1] == 177){//OP_CHECKLOCKTIMEVERIFY
11381289
// hodl script (not signed)
11391290
return {'type':'hodl', 'signed':'false', 'signatures': 0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
1140-
} 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){
1291+
} 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))){
1292+
var signed = ((this.witness[index]) && this.witness[index].length==2) ? 'true' : 'false';
1293+
var sigs = (signed == 'true') ? 1 : 0;
1294+
var value = -1; // no value found
1295+
if((this.ins[index].script.chunks[2]) && this.ins[index].script.chunks[2].length==8){
1296+
value = coinjs.bytesToNum(this.ins[index].script.chunks[2]); // value found encoded in transaction (THIS IS NON STANDARD)
1297+
}
1298+
return {'type':'segwit', 'signed':signed, 'signatures': sigs, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), 'value': value};
1299+
1300+
/* } 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)){
1301+
alert('p2sh');
11411302
// segwit script
11421303
var signed = ((this.witness[index]) && this.witness[index].length==2) ? 'true' : 'false';
11431304
var sigs = (signed == 'true') ? 1 : 0;
@@ -1146,6 +1307,7 @@
11461307
value = coinjs.bytesToNum(this.ins[index].script.chunks[2]); // value found encoded in transaction (THIS IS NON STANDARD)
11471308
}
11481309
return {'type':'segwit', 'signed':signed, 'signatures': sigs, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), 'value': value};
1310+
*/
11491311
} 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
11501312
// multisig script, with signature(s) included
11511313
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 @@
13671529

13681530
var wif2 = coinjs.wif2pubkey(wif);
13691531
var segwit = coinjs.segwitAddress(wif2['pubkey']);
1532+
var bech32 = coinjs.bech32Address(wif2['pubkey']);
13701533

1371-
if(segwit['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])){
1534+
if((segwit['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])) || (bech32['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0]))){
13721535
var txhash = this.transactionHashSegWitV0(index, shType);
1536+
13731537
if(txhash.result == 1){
1538+
13741539
var segwitHash = Crypto.util.hexToBytes(txhash.hash);
13751540
var signature = this.transactionSig(index, wif, shType, segwitHash);
1376-
1541+
13771542
// remove any non standard data we store, i.e. input value
13781543
var script = coinjs.script();
13791544
script.writeBytes(this.ins[index].script.chunks[0]);
@@ -1396,9 +1561,13 @@
13961561
for(var y = 0; y < this.witness.length; y++){
13971562
if(!witness_used.includes(y)){
13981563
var sw = coinjs.segwitAddress(this.witness[y][1]);
1399-
if(sw['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])){
1564+
var b32 = coinjs.bech32Address(this.witness[y][1]);
1565+
if((sw['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])) || (b32['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0]))){
14001566
witness_order.push(this.witness[y]);
14011567
witness_used.push(y);
1568+
if(b32['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])){
1569+
this.ins[index].script = coinjs.script();
1570+
}
14021571
break;
14031572
}
14041573
}

0 commit comments

Comments
 (0)