mirror of
https://github.com/ok2/coinbin.git
synced 2026-05-09 18:15:23 +02:00
first stage of adding bech32 support added to coinb.in
This commit is contained in:
+183
-14
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user