Update Stdop: multisig-bech32 (p2wsh)

This commit is contained in:
sairajzero 2022-11-27 01:59:34 +05:30
parent a1c539c0d7
commit 2c5d02d3c0
2 changed files with 916 additions and 607 deletions

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //btcOperator v1.0.13c (function (EXPORTS) { //btcOperator v1.0.14
/* BTC Crypto and API Operator */ /* BTC Crypto and API Operator */
const btcOperator = EXPORTS; const btcOperator = EXPORTS;
@ -102,7 +102,7 @@
if (!addr) if (!addr)
return undefined; return undefined;
let type = coinjs.addressDecode(addr).type; let type = coinjs.addressDecode(addr).type;
if (["standard", "multisig", "bech32"].includes(type)) if (["standard", "multisig", "bech32", "multisigBech32"].includes(type))
return type; return type;
else else
return false; return false;
@ -398,6 +398,7 @@
return reject("Insufficient Balance"); return reject("Insufficient Balance");
let addr = senders[rec_args.n], let addr = senders[rec_args.n],
rs = redeemScripts[rec_args.n]; rs = redeemScripts[rec_args.n];
let addr_type = coinjs.addressDecode(addr).type;
let size_per_input = _sizePerInput(addr, rs); let size_per_input = _sizePerInput(addr, rs);
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => { fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
let utxos = result.data.txs; let utxos = result.data.txs;
@ -408,14 +409,14 @@
var script; var script;
if (!rs || !rs.length) //legacy script if (!rs || !rs.length) //legacy script
script = utxos[i].script_hex; script = utxos[i].script_hex;
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) { else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') {
//redeemScript for segwit/bech32 //redeemScript for segwit/bech32 and multisig (bech32)
let s = coinjs.script(); let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(rs)); s.writeBytes(Crypto.util.hexToBytes(rs));
s.writeOp(0); s.writeOp(0);
s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8)); s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer); script = Crypto.util.bytesToHex(s.buffer);
} else //redeemScript for multisig } else //redeemScript for multisig (segwit)
script = rs; script = rs;
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
//update track values //update track values

View File

@ -1,4 +1,4 @@
(function(GLOBAL) { //lib v1.3.1 (function (GLOBAL) { //lib v1.3.2
'use strict'; 'use strict';
/* Utility Libraries required for Standard operations /* Utility Libraries required for Standard operations
* All credits for these codes belong to their respective creators, moderators and owners. * All credits for these codes belong to their respective creators, moderators and owners.
@ -6478,6 +6478,20 @@
}; };
} }
//Return a Bech32 address for the multisig. Format is same as above
coinjs.pubkeys2MultisigAddressBech32 = function (pubkeys, required) {
var r = coinjs.pubkeys2MultisigAddress(pubkeys, required);
var program = Crypto.SHA256(Crypto.util.hexToBytes(r.redeemScript), {
asBytes: true
});
var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true)));
return {
'address': address,
'redeemScript': r.redeemScript,
'size': r.size
};
}
/* new time locked address, provide the pubkey and time necessary to unlock the funds. /* new time locked address, provide the pubkey and time necessary to unlock the funds.
when time is greater than 500000000, it should be a unix timestamp (seconds since epoch), when time is greater than 500000000, it should be a unix timestamp (seconds since epoch),
otherwise it should be the block height required before this transaction can be released. otherwise it should be the block height required before this transaction can be released.
@ -6567,6 +6581,18 @@
}; };
} }
coinjs.multisigBech32Address = function (raw_redeemscript) {
var program = Crypto.SHA256(Crypto.util.hexToBytes(raw_redeemscript), {
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': 'multisigBech32',
'redeemscript': Crypto.util.bytesToHex(program)
};
}
/* extract the redeemscript from a bech32 address */ /* extract the redeemscript from a bech32 address */
coinjs.bech32redeemscript = function (address) { coinjs.bech32redeemscript = function (address) {
var r = false; var r = false;
@ -6658,6 +6684,9 @@
} else if (o.version == coinjs.multisig) { // multisig address } else if (o.version == coinjs.multisig) { // multisig address
o.type = 'multisig'; o.type = 'multisig';
} else if (o.version == coinjs.multisigBech32) { // multisigBech32 added
o.type = 'multisigBech32';
} else if (o.version == coinjs.priv) { // wifkey } else if (o.version == coinjs.priv) { // wifkey
o.type = 'wifkey'; o.type = 'wifkey';
@ -6698,11 +6727,16 @@
} }
} catch (e) { } catch (e) {
let bech32rs = coinjs.bech32redeemscript(addr); let bech32rs = coinjs.bech32redeemscript(addr);
if (bech32rs) { if (bech32rs && bech32rs.length == 40) {
return { return {
'type': 'bech32', 'type': 'bech32',
'redeemscript': bech32rs 'redeemscript': bech32rs
}; };
} else if (bech32rs && bech32rs.length == 64) {
return {
'type': 'multisigBech32',
'redeemscript': bech32rs
};
} else { } else {
return false; return false;
} }
@ -6711,7 +6745,7 @@
/* retreive the balance from a given address */ /* retreive the balance from a given address */
coinjs.addressBalance = function (address, callback) { coinjs.addressBalance = function (address, callback) {
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=bal&address=' + address + '&r=' + securedMathRandom(), callback, "GET"); coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=bal&address=' + address + '&r=' + Math.random(), callback, "GET");
} }
/* decompress an compressed public key */ /* decompress an compressed public key */
@ -7328,11 +7362,39 @@
return r; return r;
} }
/* decode the redeemscript of a multisignature transaction for Bech32*/
r.decodeRedeemScriptBech32 = function (script) {
var r = false;
try {
var s = coinjs.script(Crypto.util.hexToBytes(script));
if ((s.chunks.length >= 3) && s.chunks[s.chunks.length - 1] == 174) { //OP_CHECKMULTISIG
r = {};
r.signaturesRequired = s.chunks[0] - 80;
var pubkeys = [];
for (var i = 1; i < s.chunks.length - 2; i++) {
pubkeys.push(Crypto.util.bytesToHex(s.chunks[i]));
}
r.pubkeys = pubkeys;
var multi = coinjs.pubkeys2MultisigAddressBech32(pubkeys, r.signaturesRequired);
r.address = multi['address'];
r.type = 'multisig__'; // using __ for now to differentiat from the other object .type == "multisig"
var rs = Crypto.util.bytesToHex(s.buffer);
r.redeemscript = rs;
}
} catch (e) {
// console.log(e);
r = false;
}
return r;
}
/* create output script to spend */ /* create output script to spend */
r.spendToScript = function (address) { r.spendToScript = function (address) {
var addr = coinjs.addressDecode(address); var addr = coinjs.addressDecode(address);
var s = coinjs.script(); var s = coinjs.script();
if (addr.type == "bech32") { if (addr.type == "bech32" || addr.type == "multisigBech32") {
s.writeOp(0); s.writeOp(0);
s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript)); s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript));
} else if (addr.version == coinjs.multisig) { // multisig address } else if (addr.version == coinjs.multisig) { // multisig address
@ -7490,12 +7552,12 @@
/* list unspent transactions */ /* list unspent transactions */
r.listUnspent = function (address, callback) { r.listUnspent = function (address, callback) {
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=unspent&address=' + address + '&r=' + securedMathRandom(), callback, "GET"); coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=unspent&address=' + address + '&r=' + Math.random(), callback, "GET");
} }
/* list transaction data */ /* list transaction data */
r.getTransaction = function (txid, callback) { r.getTransaction = function (txid, callback) {
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=bitcoin&request=gettransaction&txid=' + txid + '&r=' + securedMathRandom(), callback, "GET"); coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=bitcoin&request=gettransaction&txid=' + txid + '&r=' + Math.random(), callback, "GET");
} }
/* add unspent to transaction */ /* add unspent to transaction */
@ -7525,7 +7587,7 @@
var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue; var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue;
var scr = script || u.getElementsByTagName("script")[0].childNodes[0].nodeValue; var scr = script || u.getElementsByTagName("script")[0].childNodes[0].nodeValue;
if (segwit) { if (segwit) { //also for MULTISIG_BECH32 (p2wsh-multisig)(script = raw_redeemscript; for p2wsh-multisig)
/* this is a small hack to include the value with the redeemscript to make the signing procedure smoother. /* this is a small hack to include the value with the redeemscript to make the signing procedure smoother.
It is not standard and removed during the signing procedure. */ It is not standard and removed during the signing procedure. */
@ -7664,7 +7726,7 @@
// start redeem script check // start redeem script check
var extract = this.extractScriptKey(index); var extract = this.extractScriptKey(index);
if (extract['type'] != 'segwit') { if (extract['type'] != 'segwit' && extract['type'] != 'multisig_bech32') {
return { return {
'result': 0, 'result': 0,
'fail': 'redeemscript', 'fail': 'redeemscript',
@ -7693,6 +7755,8 @@
scriptcode = scriptcode.slice(1); scriptcode = scriptcode.slice(1);
scriptcode.unshift(25, 118, 169); scriptcode.unshift(25, 118, 169);
scriptcode.push(136, 172); scriptcode.push(136, 172);
} else if (scriptcode[0] > 80) {
scriptcode.unshift(scriptcode.length)
} }
var value = coinjs.numToBytes(extract['value'], 8); var value = coinjs.numToBytes(extract['value'], 8);
@ -7855,11 +7919,26 @@
'signatures': 0, 'signatures': 0,
'script': Crypto.util.bytesToHex(this.ins[index].script.buffer) 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)
}; };
} else if (this.ins[index].script.chunks.length == 3 && this.ins[index].script.chunks[0][0] >= 80 && this.ins[index].script.chunks[0][this.ins[index].script.chunks[0].length - 1] == 174 && this.ins[index].script.chunks[1] == 0) { //OP_CHECKMULTISIG_BECH32
// multisig bech32 script
let last_index = this.ins[index].script.chunks.length - 1;
var value = -1;
if (last_index >= 2 && this.ins[index].script.chunks[last_index].length == 8) {
value = coinjs.bytesToNum(this.ins[index].script.chunks[last_index]); // value found encoded in transaction (THIS IS NON STANDARD)
}
var sigcount = (!this.witness[index]) ? 0 : this.witness[index].length - 2;
return {
'type': 'multisig_bech32',
'signed': 'false',
'signatures': sigcount,
'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]),
'value': value
};
} else if (this.ins[index].script.chunks.length == 0) { } else if (this.ins[index].script.chunks.length == 0) {
// empty // empty
//bech32 witness check //bech32 witness check
var signed = ((this.witness[index]) && this.witness[index].length == 2) ? 'true' : 'false'; var signed = ((this.witness[index]) && this.witness[index].length >= 2) ? 'true' : 'false';
var sigs = (signed == 'true') ? 1 : 0; var sigs = (signed == 'true') ? (!this.witness[index][0] ? this.witness[index].length - 2 : 1) : 0;
return { return {
'type': 'empty', 'type': 'empty',
'signed': signed, 'signed': signed,
@ -8038,6 +8117,71 @@
return true; return true;
} }
r.signmultisig_bech32 = function (index, wif, sigHashType) {
function scriptListPubkey(redeemScript) {
var r = {};
for (var i = 1; i < redeemScript.chunks.length - 2; i++) {
r[i] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(Crypto.util.bytesToHex(redeemScript.chunks[i])));
}
return r;
}
function scriptListSigs(sigList) {
let r = {};
var c = 0;
if (Array.isArray(sigList)) {
for (let i = 1; i < sigList.length - 1; i++) {
c++;
r[c] = Crypto.util.hexToBytes(sigList[i]);
}
}
return r;
}
var redeemScript = Crypto.util.bytesToHex(this.ins[index].script.chunks[0]); //redeemScript
if (!coinjs.isArray(this.witness)) {
this.witness = new Array(this.ins.length);
this.witness.fill([]);
}
var pubkeyList = scriptListPubkey(coinjs.script(redeemScript));
var sigsList = scriptListSigs(this.witness[index]);
let decode_rs = coinjs.script().decodeRedeemScriptBech32(redeemScript);
var shType = sigHashType || 1;
var txhash = this.transactionHashSegWitV0(index, shType);
if (txhash.result == 1 && decode_rs.pubkeys.includes(coinjs.wif2pubkey(wif)['pubkey'])) {
var segwitHash = Crypto.util.hexToBytes(txhash.hash);
var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType, segwitHash)); //CHECK THIS
sigsList[coinjs.countObject(sigsList) + 1] = signature;
var w = [];
for (let x in pubkeyList) {
for (let y in sigsList) {
var sighash = this.transactionHashSegWitV0(index, sigsList[y].slice(-1)[0] * 1).hash
sighash = Crypto.util.hexToBytes(sighash);
if (coinjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) {
w.push((Crypto.util.bytesToHex(sigsList[y])))
}
}
}
// when enough signatures collected, remove any non standard data we store, i.e. input value
if (w.length >= decode_rs.signaturesRequired) {
this.ins[index].script = coinjs.script();
}
w.unshift(0);
w.push(redeemScript);
this.witness[index] = w;
}
}
/* sign a multisig input */ /* sign a multisig input */
r.signmultisig = function (index, wif, sigHashType) { r.signmultisig = function (index, wif, sigHashType) {
@ -8187,6 +8331,9 @@
} else if (d['type'] == 'multisig') { } else if (d['type'] == 'multisig') {
this.signmultisig(i, wif, shType); this.signmultisig(i, wif, shType);
} else if (d['type'] == 'multisig_bech32' && d['signed'] == "false") {
this.signmultisig_bech32(i, wif, shType);
} else if (d['type'] == 'segwit') { } else if (d['type'] == 'segwit') {
this.signsegwit(i, wif, shType); this.signsegwit(i, wif, shType);
@ -8240,6 +8387,63 @@
return Crypto.util.bytesToHex(buffer); return Crypto.util.bytesToHex(buffer);
} }
//Utility funtion added to directly compute signatures without transaction index
r.transactionSigNoIndex = function (wif, sigHashType, txhash) {
function serializeSig(r, s) {
var rBa = r.toByteArraySigned();
var sBa = s.toByteArraySigned();
var sequence = [];
sequence.push(0x02); // INTEGER
sequence.push(rBa.length);
sequence = sequence.concat(rBa);
sequence.push(0x02); // INTEGER
sequence.push(sBa.length);
sequence = sequence.concat(sBa);
sequence.unshift(sequence.length);
sequence.unshift(0x30); // SEQUENCE
return sequence;
}
var shType = sigHashType || 1;
var hash = Crypto.util.hexToBytes(txhash);
if (hash) {
var curve = EllipticCurve.getSECCurveByName("secp256k1");
var key = coinjs.wif2privkey(wif);
var priv = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(key['privkey']));
var n = curve.getN();
var e = BigInteger.fromByteArrayUnsigned(hash);
var badrs = 0
do {
var k = this.deterministicK(wif, hash, badrs);
var G = curve.getG();
var Q = G.multiply(k);
var r = Q.getX().toBigInteger().mod(n);
var s = k.modInverse(n).multiply(e.add(priv.multiply(r))).mod(n);
badrs++
} while (r.compareTo(BigInteger.ZERO) <= 0 || s.compareTo(BigInteger.ZERO) <= 0);
// Force lower s values per BIP62
var halfn = n.shiftRight(1);
if (s.compareTo(halfn) > 0) {
s = n.subtract(s);
};
var sig = serializeSig(r, s);
sig.push(parseInt(shType, 10));
return Crypto.util.bytesToHex(sig);
} else {
return false;
}
}
/* deserialize a transaction */ /* deserialize a transaction */
r.deserialize = function (buffer) { r.deserialize = function (buffer) {
if (typeof buffer == "string") { if (typeof buffer == "string") {
@ -8582,12 +8786,116 @@
return count; return count;
} }
//Nine utility functions added for generating transaction hashes and verification of signatures
coinjs.changeEndianness = (string) => {
const result = [];
let len = string.length - 2;
while (len >= 0) {
result.push(string.substr(len, 2));
len -= 2;
}
return result.join('');
}
coinjs.getTransactionHash = function (transaction_in_hex, changeOutputEndianess) {
var x1, x2, x3, x4, x5;
x1 = Crypto.util.hexToBytes(transaction_in_hex);
x2 = Crypto.SHA256(x1);
x3 = Crypto.util.hexToBytes(x2);
x4 = Crypto.SHA256(x3);
x5 = coinjs.changeEndianness(x4);
if (changeOutputEndianess == true) { x5 = x5 } else if ((typeof changeOutputEndianess == 'undefined') || (changeOutputEndianess == false)) { x5 = x4 };
return x5;
}
coinjs.compressedToUncompressed = function (compressed) {
var t1, t2;
var curve = EllipticCurve.getSECCurveByName("secp256k1");
t1 = curve.curve.decodePointHex(compressed);
t2 = curve.curve.encodePointHex(t1);
return t2;
}
coinjs.uncompressedToCompressed = function (uncompressed) {
var t1, t2, t3;
t1 = uncompressed.charAt(uncompressed.length - 1)
t2 = parseInt(t1, 10);
//Check if the last digit is odd
if (t2 % 2 == 1) { t3 = "03"; } else { t3 = "02" };
return t3 + uncompressed.substr(2, 64);
}
coinjs.verifySignatureHex = function (hashHex, sigHex, pubHexCompressed) {
var h1, s1, p1, p2;
h1 = Crypto.util.hexToBytes(hashHex);
s1 = Crypto.util.hexToBytes(sigHex);
p1 = coinjs.compressedToUncompressed(pubHexCompressed);
p2 = Crypto.util.hexToBytes(p1);
return coinjs.verifySignature(h1, s1, p2);
}
coinjs.generateBitcoinSignature = function (private_key, hash, sighash_type_int = 1) {
var wif, tx1;
if (private_key.length < 60) { wif = private_key } else { wif = coinjs.privkey2wif(private_key) };
tx1 = coinjs.transaction();
return tx1.transactionSigNoIndex(wif, sighash_type_int, hash);
}
coinjs.dSHA256 = function (data) {
var t1, t2, t3;
t1 = Crypto.SHA256(Crypto.util.hexToBytes(data));
t2 = Crypto.util.hexToBytes(t1);
t3 = Crypto.SHA256(t2);
return t3;
}
coinjs.fromBitcoinAmountFormat = function (data) {
var x1, x2, x3;
x1 = coinjs.changeEndianness(data);
x2 = parseInt(x1, 16);
x3 = x2 / (10 ** 8);
return x3;
}
coinjs.toBitcoinAmountFormat = function (countBitcoin) {
var t2, t3, t4, t5;
t2 = countBitcoin * 10 ** 8;
t3 = t2.toString(16);
t4 = coinjs.changeEndianness(t3);
t5 = t4.padEnd(16, "0");
return t5;
}
coinjs.scriptcodeCreatorBasic = function (scriptpubkey) {
var t1, t2, t3, t4;
if (scriptpubkey.substr(0, 4) == "0014") {
//Scriptpubkey case
t1 = scriptpubkey.slice(2);
t2 = "1976a9" + t1 + "88ac";
} else {
//Redeemscript case
t3 = (scriptpubkey.length) / 2;
t4 = t3.toString(16);
t2 = t4 + scriptpubkey;
}
return t2;
}
coinjs.ripemd160sha256 = function (data) {
var t1, t2;
t1 = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(data), { asBytes: true }), { asBytes: true });
t2 = Crypto.util.bytesToHex(t1)
return t2;
}
coinjs.random = function (length) { coinjs.random = function (length) {
var r = ""; var r = "";
var l = length || 25; var l = length || 25;
var chars = "!$%^&*()_+{}:@~?><|\./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; var chars = "!$%^&*()_+{}:@~?><|\./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
for (let x = 0; x < l; x++) { for (let x = 0; x < l; x++) {
r += chars.charAt(Math.floor(securedMathRandom() * 62)); r += chars.charAt(Math.floor(Math.random() * 62));
} }
return r; return r;
} }