From 6288dc82d9007b5d0faf769f25be49d7ad212b05 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 23 Feb 2023 02:04:16 +0530 Subject: [PATCH 1/8] lib v1.4.1b: Minor fixes in coinjs multisig-addr - pubkeys2MultisigAddressBech32: returns another value 'scripthash' - Fixed multisigBech32Address: returning scripthash as redeemScript. (now returns both scripthash and redeemScript --- lib.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib.js b/lib.js index d0871b1..50305cf 100644 --- a/lib.js +++ b/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.4.1a +(function (GLOBAL) { //lib v1.4.1b 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -6704,6 +6704,7 @@ return { 'address': address, 'redeemScript': r.redeemScript, + 'scripthash': Crypto.util.bytesToHex(program), 'size': r.size }; } @@ -6797,15 +6798,16 @@ }; } - coinjs.multisigBech32Address = function (raw_redeemscript) { - var program = Crypto.SHA256(Crypto.util.hexToBytes(raw_redeemscript), { + coinjs.multisigBech32Address = function (redeemscript) { + var program = Crypto.SHA256(Crypto.util.hexToBytes(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) + 'redeemScript': redeemscript, + 'scripthash': Crypto.util.bytesToHex(program) }; } @@ -7803,7 +7805,7 @@ var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue; var scr = script || u.getElementsByTagName("script")[0].childNodes[0].nodeValue; - if (segwit) { //also for MULTISIG_BECH32 (p2wsh-multisig)(script = raw_redeemscript; for p2wsh-multisig) + if (segwit) { //also for MULTISIG_BECH32 (p2wsh-multisig)(script = redeemscript; for p2wsh-multisig) /* 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. */ From 070d2198ba15b37653b47390290ceac200db4db8 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 23 Feb 2023 02:13:31 +0530 Subject: [PATCH 2/8] btcOperator v1.1.2: multisig utils - Added decodeRedeemScript: decodes the given redeemScript and return {address, pubKeys, required}. (optional bech32; default=true. if true return bech32 address. if false returns legacy multisig address) - Added convert.multisig2multisig: convert from one multisig address to another. (optional target_version; default=btc-multisig-version) - Added convert.bech2multisig: converts multisig bech32 address to multisig address. (optional target_version; default=btc-multisig-version) - Added encodeLegacy, decodeLegacy, encodeBech32, decodeBech32 to util --- btcOperator.js | 74 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/btcOperator.js b/btcOperator.js index 6925cec..ed967b9 100644 --- a/btcOperator.js +++ b/btcOperator.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //btcOperator v1.1.1 +(function (EXPORTS) { //btcOperator v1.1.2 /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; @@ -130,50 +130,84 @@ return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired); } + btcOperator.decodeRedeemScript = function (redeemScript, bech32 = true) { + let script = coinjs.script(); + let decoded = (bech32) ? + script.decodeRedeemScriptBech32(redeemScript) : + script.decodeRedeemScript(redeemScript); + if (!decoded) + return null; + return { + address: decoded.address, + pubKeys: decoded.pubkeys, + redeemScript: decoded.redeemscript, + required: decoded.signaturesRequired + } + + } + //convert from one blockchain to another blockchain (target version) btcOperator.convert = {}; btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) { - let keyHex = decodeLegacy(source_wif).hex; + let keyHex = util.decodeLegacy(source_wif).hex; if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex)) return null; else - return encodeLegacy(keyHex, target_version); + return util.encodeLegacy(keyHex, target_version); } btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) { - let rawHex = decodeLegacy(source_addr).hex; + let rawHex = util.decodeLegacy(source_addr).hex; if (!rawHex) return null; else - return encodeLegacy(rawHex, target_version); + return util.encodeLegacy(rawHex, target_version); } btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { - let rawHex = decodeLegacy(source_addr).hex; + let rawHex = util.decodeLegacy(source_addr).hex; if (!rawHex) return null; else - return encodeBech32(rawHex, target_version, target_hrp); + return util.encodeBech32(rawHex, target_version, target_hrp); } btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { - let rawHex = decodeBech32(source_addr).hex; + let rawHex = util.decodeBech32(source_addr).hex; if (!rawHex) return null; else - return encodeBech32(rawHex, target_version, target_hrp); + return util.encodeBech32(rawHex, target_version, target_hrp); } btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) { - let rawHex = decodeBech32(source_addr).hex; + let rawHex = util.decodeBech32(source_addr).hex; if (!rawHex) return null; else - return encodeLegacy(rawHex, target_version); + return util.encodeLegacy(rawHex, target_version); } - function decodeLegacy(source) { + btcOperator.convert.multisig2multisig = function (source_addr, target_version = coinjs.multisig) { + let rawHex = util.decodeLegacy(source_addr).hex; + if (!rawHex) + return null; + else + return util.encodeLegacy(rawHex, target_version); + } + + btcOperator.convert.bech2multisig = function (source_addr, target_version = coinjs.multisig) { + let rawHex = util.decodeBech32(source_addr).hex; + if (!rawHex) + return null; + else { + rawHex = Crypto.util.bytesToHex(ripemd160(Crypto.util.hexToBytes(rawHex), { asBytes: true })); + return util.encodeLegacy(rawHex, target_version); + } + } + + util.decodeLegacy = function (source) { var decode = coinjs.base58decode(source); var raw = decode.slice(0, decode.length - 4), checksum = decode.slice(decode.length - 4); @@ -183,7 +217,7 @@ asBytes: true }); if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) - return null; + return false; let version = raw.shift(); return { version: version, @@ -191,7 +225,7 @@ } } - function encodeLegacy(hex, version) { + util.encodeLegacy = function (hex, version) { var bytes = Crypto.util.hexToBytes(hex); bytes.unshift(version); var hash = Crypto.SHA256(Crypto.SHA256(bytes, { @@ -203,10 +237,10 @@ return coinjs.base58encode(bytes.concat(checksum)); } - function decodeBech32(source) { + util.decodeBech32 = function (source) { let decode = coinjs.bech32_decode(source); if (!decode) - return null; + return false; var raw = decode.data; let version = raw.shift(); raw = coinjs.bech32_convert(raw, 5, 8, false); @@ -217,7 +251,7 @@ } } - function encodeBech32(hex, version, hrp) { + util.encodeBech32 = function (hex, version, hrp) { var bytes = Crypto.util.hexToBytes(hex); bytes = coinjs.bech32_convert(bytes, 8, 5, true); bytes.unshift(version) @@ -706,13 +740,13 @@ var address; switch (out.script.chunks[0]) { case 0: //bech32, multisig-bech32 - address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp); + address = util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp); break; case 169: //segwit, multisig-segwit - address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig); + address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig); break; case 118: //legacy - address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub); + address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub); } return { address, From e4fd63912c09e23b175d8fc71a590f76a1a329da Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 23 Feb 2023 02:49:42 +0530 Subject: [PATCH 3/8] floCrypto v2.3.5: multisig address utils - Added toMultisigFloID: converts given multisig address to FLO multisig ID - Improved verifyPubKey: also support verifying multisig redeemScript for the multisig address - Improved isSameAddr: supports multisig bech32 - Improved decodeAddress: supports multisig bech32 --- floCrypto.js | 56 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/floCrypto.js b/floCrypto.js index 19ffedc..8f0da40 100644 --- a/floCrypto.js +++ b/floCrypto.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCrypto v2.3.4a +(function (EXPORTS) { //floCrypto v2.3.5 /* FLO Crypto Operators */ 'use strict'; const floCrypto = EXPORTS; @@ -290,13 +290,15 @@ return false; } - //Check the public-key for the address (any blockchain) + //Check the public-key (or redeem-script) for the address (any blockchain) floCrypto.verifyPubKey = function (pubKeyHex, address) { - let raw = decodeAddress(address), - pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { - asBytes: true - }))); - return raw ? pub_hash === raw.hex : false; + let raw = decodeAddress(address); + if (!raw) + return; + let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true }))); + if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig + raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true })); + return pub_hash === raw.hex; } //Convert the given address (any blockchain) to equivalent floID @@ -306,7 +308,7 @@ let raw = decodeAddress(address); if (!raw) return; - else if (options) { + else if (options) { //if (optional) version check is passed if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version))) return; if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version))) @@ -321,6 +323,35 @@ return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4))); } + //Convert the given multisig address (any blockchain) to equivalent multisig floID + floCrypto.toMultisigFloID = function (address, options = null) { + if (!address) + return; + let raw = decodeAddress(address); + if (!raw) + return; + else if (options) { //if (optional) version check is passed + if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version))) + return; + if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version))) + return; + } + if (typeof raw.bech_version !== 'undefined') { + if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes + //multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech + raw.bytes = ripemd160(raw.bytes, { + asBytes: true + }); + } + raw.bytes.unshift(bitjs.multisig); + let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, { + asBytes: true + }), { + asBytes: true + }); + return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4))); + } + //Checks if the given addresses (any blockchain) are same (w.r.t keys) floCrypto.isSameAddr = function (addr1, addr2) { if (!addr1 || !addr2) @@ -329,8 +360,13 @@ raw2 = decodeAddress(addr2); if (!raw1 || !raw2) return false; - else + else { + if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig + raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true })); + if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig + raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true })); return raw1.hex === raw2.hex; + } } const decodeAddress = floCrypto.decodeAddr = function (address) { @@ -350,7 +386,7 @@ hex: Crypto.util.bytesToHex(bytes), bytes } - } else if (address.length == 42) { //bech encoding + } else if (address.length == 42 || address.length == 62) { //bech encoding let decode = coinjs.bech32_decode(address); if (decode) { let bytes = decode.data; From 24415b835c8665f9310c27cd05f76a38fcf375d1 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 23 Feb 2023 22:49:32 +0530 Subject: [PATCH 4/8] lib v1.4.2: (flo) bitjs.transaction deserialize - pass tx_data (hex or byte array) to bitjs.transaction to deserialize the transaction ie, `tx = bitjs.transaction(tx_data)` - invoking without any parameter `bitjs.transaction()` will create an empty tx as before (no change) --- lib.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/lib.js b/lib.js index 50305cf..64131f2 100644 --- a/lib.js +++ b/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.4.1b +(function (GLOBAL) { //lib v1.4.2 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -4492,7 +4492,7 @@ }; } - bitjs.transaction = function () { + bitjs.transaction = function (tx_data = undefined) { var btrx = {}; btrx.version = 2; //flochange look at this version btrx.inputs = []; @@ -4992,6 +4992,78 @@ return Crypto.util.bytesToHex(buffer); } + /* deserialize a transaction */ + function deserialize(buffer) { + if (typeof buffer == "string") { + buffer = Crypto.util.hexToBytes(buffer) + } + + var pos = 0; + + var readAsInt = function (bytes) { + if (bytes == 0) return 0; + pos++; + return buffer[pos - 1] + readAsInt(bytes - 1) * 256; + } + + var readVarInt = function () { + pos++; + if (buffer[pos - 1] < 253) { + return buffer[pos - 1]; + } + return readAsInt(buffer[pos - 1] - 251); + } + + var readBytes = function (bytes) { + pos += bytes; + return buffer.slice(pos - bytes, pos); + } + + var readVarString = function () { + var size = readVarInt(); + return readBytes(size); + } + + var bytesToStr = function (bytes) { + return bytes.map(b => String.fromCharCode(b)).join(''); + } + + const self = btrx; + + self.version = readAsInt(4); + + var ins = readVarInt(); + for (var i = 0; i < ins; i++) { + self.inputs.push({ + outpoint: { + hash: Crypto.util.bytesToHex(readBytes(32).reverse()), + index: readAsInt(4) + }, + script: readVarString(), + sequence: readAsInt(4) + }); + } + + var outs = readVarInt(); + for (var i = 0; i < outs; i++) { + self.outputs.push({ + value: bitjs.bytesToNum(readBytes(8)), + script: readVarString() + }); + } + + self.lock_time = readAsInt(4); + + //flochange - floData field + self.floData = bytesToStr(readVarString()); + + return self; + } + + //deserialize the data if passed + if (tx_data) + deserialize(tx_data); + return btrx; } From 09e306d3128d8744c1ffa587c37751424f019a4d Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 23 Feb 2023 23:07:54 +0530 Subject: [PATCH 5/8] floBlockchainAPI v2.4.1: Improvements and signTx - Added createTx: create unsigned tx (resolves tx-hex ) - Updated createMultisigTx: resolve tx-hex instead of tx object - Added signTx: (synchronized fn) sign the given tx (hex or object) with given private key and returns the signed txhex --- floBlockchainAPI.js | 61 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index d4f9bf6..7cc90c7 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.4.0 +(function (EXPORTS) { //floBlockchainAPI v2.4.1 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -121,8 +121,8 @@ }); } - //Send Tx to blockchain - const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { + //create a transaction with single sender + const createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); @@ -130,8 +130,6 @@ return reject(`Invalid address : ${senderAddr}`); else if (!floCrypto.validateFloID(receiverAddr)) return reject(`Invalid address : ${receiverAddr}`); - else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr)) - return reject("Invalid Private key!"); else if (typeof sendAmt !== 'number' || sendAmt <= 0) return reject(`Invalid sendAmt : ${sendAmt}`); @@ -175,15 +173,36 @@ if (change > DEFAULT.minChangeAmt) trx.addoutput(senderAddr, change); trx.addflodata(floData.replace(/\n/g, ' ')); - var signedTxHash = trx.sign(privKey, 1); - broadcastTx(signedTxHash) - .then(txid => resolve(txid)) - .catch(error => reject(error)) + resolve(trx); } }).catch(error => reject(error)) }).catch(error => reject(error)) }).catch(error => reject(error)) }).catch(error => reject(error)) + }) + } + + floBlockchainAPI.createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo) + .then(trx => resolve(trx.serialize())) + .catch(error => reject(error)) + }) + } + + //Send Tx to blockchain + const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + if (!floCrypto.validateFloID(senderAddr, true)) + return reject(`Invalid address : ${senderAddr}`); + else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr)) + return reject("Invalid Private key!"); + createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo).then(trx => { + var signedTxHash = trx.sign(privKey, 1); + broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }).catch(error => reject(error)) }); } @@ -419,7 +438,7 @@ } //Create a multisig transaction - const createMultisigTx = floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { + const createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { var multisig = floCrypto.decodeRedeemScript(redeemScript); @@ -499,6 +518,15 @@ }); } + //Same as above, but explict call should return serialized tx-hex + floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo) + .then(trx => resolve(trx.serialize())) + .catch(error => reject(error)) + }) + } + //Create and send multisig transaction const sendMultisigTx = floBlockchainAPI.sendMultisigTx = function (redeemScript, privateKeys, receivers, amounts, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { @@ -538,6 +566,19 @@ }) } + floBlockchainAPI.signTx = function (tx, privateKey, sighashtype = 1) { + if (!floCrypto.getFloID(privateKey)) + throw "Invalid Private key"; + //deserialize if needed + if (!(tx instanceof Object)) { + if (typeof tx === 'string' || Array.isArray(tx)) + tx = bitjs.transaction(tx) + } else if (typeof tx.sign !== 'function') + throw "Tx is not a instance of transaction"; + var signedTxHex = tx.sign(privateKey); + return signedTxHex; + } + //Broadcast signed Tx in blockchain using API const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { return new Promise((resolve, reject) => { From daec31c1ac16c33372bc9020a1f2131f0269e8b6 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 23 Feb 2023 23:24:55 +0530 Subject: [PATCH 6/8] btcOperator v1.1.2a: bug fix - Fixed: checkIfSameTx loop in comparing outputs using incorrect length --- btcOperator.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/btcOperator.js b/btcOperator.js index ed967b9..ff86fb9 100644 --- a/btcOperator.js +++ b/btcOperator.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //btcOperator v1.1.2 +(function (EXPORTS) { //btcOperator v1.1.2a /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; @@ -703,12 +703,15 @@ btcOperator.checkIfSameTx = function (tx1, tx2) { tx1 = deserializeTx(tx1); tx2 = deserializeTx(tx2); + //compare input and output length if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length) return false; + //compare inputs for (let i = 0; i < tx1.ins.length; i++) if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index) return false; - for (let i = 0; i < tx2.ins.length; i++) + //compare outputs + for (let i = 0; i < tx1.outs.length; i++) if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer)) return false; return true; From 7b298ceb50ba696c3c0914e696d3869643e48b2b Mon Sep 17 00:00:00 2001 From: sairajzero Date: Fri, 24 Feb 2023 00:04:06 +0530 Subject: [PATCH 7/8] floBlockchainAPI v2.4.2: tx utility fns - Added checkSigned: check if the tx is signed or not (if optional args is passed as false, returns a detailed array indicating if each input is signed or not) - Added checkIfSameTx: check if the passed 2 tx are same or not (ie, input, output and flodata are same or not) - Added transactionID: returns the transaction id of the tx hex --- floBlockchainAPI.js | 84 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 7cc90c7..76ea569 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.4.1 +(function (EXPORTS) { //floBlockchainAPI v2.4.2 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -566,19 +566,89 @@ }) } + function deserializeTx(tx) { + if (typeof tx === 'string' || Array.isArray(tx)) { + try { + tx = bitjs.transaction(tx); + } catch { + throw "Invalid transaction hex"; + } + } else if (typeof tx !== 'object' || typeof tx.sign !== 'function') + throw "Invalid transaction object"; + return tx; + } + floBlockchainAPI.signTx = function (tx, privateKey, sighashtype = 1) { if (!floCrypto.getFloID(privateKey)) throw "Invalid Private key"; //deserialize if needed - if (!(tx instanceof Object)) { - if (typeof tx === 'string' || Array.isArray(tx)) - tx = bitjs.transaction(tx) - } else if (typeof tx.sign !== 'function') - throw "Tx is not a instance of transaction"; - var signedTxHex = tx.sign(privateKey); + tx = deserializeTx(tx); + var signedTxHex = tx.sign(privateKey, sighashtype); return signedTxHex; } + floBlockchainAPI.checkSigned = function (tx, bool = true) { + tx = deserializeTx(tx); + let n = []; + for (let i = 0; i < tx.inputs.length; i++) { + var s = tx.scriptDecode(i); + if (s['type'] === 'scriptpubkey') + n.push(s.signed); + else if (s['type'] === 'multisig') { + var rs = tx.decodeRedeemScript(s['rs']); + let x = { + s: 0, + r: rs['required'], + t: rs['pubkeys'].length + }; + //check input script for signatures + var script = Array.from(tx.inputs[i].script); + if (script[0] == 0) { //script with signatures + script = tx.parseScript(script); + for (var k = 0; k < script.length; k++) + if (Array.isArray(script[k]) && script[k][0] == 48) //0x30 DERSequence + x.s++; + } + //validate counts + if (x.r > x.t) + throw "signaturesRequired is more than publicKeys"; + else if (x.s < x.r) + n.push(x); + else + n.push(true); + } + } + return bool ? !(n.filter(x => x !== true).length) : n; + } + + floBlockchainAPI.checkIfSameTx = function (tx1, tx2) { + tx1 = deserializeTx(tx1); + tx2 = deserializeTx(tx2); + //compare input and output length + if (tx1.inputs.length !== tx2.inputs.length || tx1.outputs.length !== tx2.outputs.length) + return false; + //compare flodata + if (tx1.floData !== tx2.floData) + return false + //compare inputs + for (let i = 0; i < tx1.inputs.length; i++) + if (tx1.inputs[i].outpoint.hash !== tx2.inputs[i].outpoint.hash || tx1.inputs[i].outpoint.index !== tx2.inputs[i].outpoint.index) + return false; + //compare outputs + for (let i = 0; i < tx1.outputs.length; i++) + if (tx1.outputs[i].value !== tx2.outputs[i].value || Crypto.util.bytesToHex(tx1.outputs[i].script) !== Crypto.util.bytesToHex(tx2.outputs[i].script)) + return false; + return true; + } + + floBlockchainAPI.transactionID = function (tx) { + tx = deserializeTx(tx); + let clone = bitjs.clone(tx); + let raw_bytes = Crypto.util.hexToBytes(clone.serialize()); + let txid = Crypto.SHA256(Crypto.SHA256(raw_bytes, { asBytes: true }), { asBytes: true }).reverse(); + return Crypto.util.bytesToHex(txid); + } + //Broadcast signed Tx in blockchain using API const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { return new Promise((resolve, reject) => { From 9db998c17652b3c1151cc60d00e5c3c485129e0f Mon Sep 17 00:00:00 2001 From: sairajzero Date: Fri, 24 Feb 2023 00:46:00 +0530 Subject: [PATCH 8/8] floBlockchainAPI v2.4.3: utility fns - Added parseTransaction: parse the given txhex - Added Sat_to_FLO and FLO_to_Sat to .util --- floBlockchainAPI.js | 66 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 76ea569..35f1e0a 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.4.2 +(function (EXPORTS) { //floBlockchainAPI v2.4.3 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -15,6 +15,13 @@ receiverID: floGlobals.adminID }; + const SATOSHI_IN_BTC = 1e8; + + const util = floBlockchainAPI.util = {}; + + util.Sat_to_FLO = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8)); + util.FLO_to_Sat = value => parseInt(value * SATOSHI_IN_BTC); + Object.defineProperties(floBlockchainAPI, { sendAmt: { get: () => DEFAULT.sendAmt, @@ -587,7 +594,7 @@ return signedTxHex; } - floBlockchainAPI.checkSigned = function (tx, bool = true) { + const checkSigned = floBlockchainAPI.checkSigned = function (tx, bool = true) { tx = deserializeTx(tx); let n = []; for (let i = 0; i < tx.inputs.length; i++) { @@ -649,6 +656,61 @@ return Crypto.util.bytesToHex(txid); } + const getTxOutput = (txid, i) => new Promise((resolve, reject) => { + fetch_api(`api/tx/${txid}`) + .then(result => resolve(result.vout[i])) + .catch(error => reject(error)) + }); + + function getOutputAddress(outscript) { + var bytes, version; + switch (outscript[0]) { + case 118: //legacy + bytes = outscript.slice(3, outscript.length - 2); + version = bitjs.pub; + break + case 169: //multisig + bytes = outscript.slice(2, outscript.length - 1); + version = bitjs.multisig; + break; + default: return; //unknown + } + bytes.unshift(version); + var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), { asBytes: true }); + var checksum = hash.slice(0, 4); + return bitjs.Base58.encode(bytes.concat(checksum)); + } + + floBlockchainAPI.parseTransaction = function (tx) { + return new Promise((resolve, reject) => { + tx = deserializeTx(tx); + let result = {}; + let promises = []; + //Parse Inputs + for (let i = 0; i < tx.inputs.length; i++) + promises.push(getTxOutput(tx.inputs[i].outpoint.hash, tx.inputs[i].outpoint.index)); + Promise.all(promises).then(inputs => { + result.inputs = inputs.map(inp => Object({ + address: inp.scriptPubKey.addresses[0], + value: parseFloat(inp.value) + })); + let signed = checkSigned(tx, false); + result.inputs.forEach((inp, i) => inp.signed = signed[i]); + //Parse Outputs + result.outputs = tx.outputs.map(out => Object({ + address: getOutputAddress(out.script), + value: util.Sat_to_FLO(out.value) + })) + //Parse Totals + result.total_input = parseFloat(result.inputs.reduce((a, inp) => a += inp.value, 0).toFixed(8)); + result.total_output = parseFloat(result.outputs.reduce((a, out) => a += out.value, 0).toFixed(8)); + result.fee = parseFloat((result.total_input - result.total_output).toFixed(8)); + result.floData = tx.floData; + resolve(result); + }).catch(error => reject(error)) + }) + } + //Broadcast signed Tx in blockchain using API const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { return new Promise((resolve, reject) => {