From 7d31dec230f4b9aafcea64c87f48b876584f7e9a Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sun, 19 Feb 2023 17:26:24 +0530 Subject: [PATCH 1/7] lib v1.3.3: Improvements to bitjs for flo - Updated tx.addflodata: will now check if the data is valid or not - Optimized tx.serialize: changed complex code for appending flodata to a simple one - Added bitjs.strToBytes: converts string (ascii) to strToBytes - Added bitjs.pubkeydecompress: decompresses the public key - Added bitjs.verifySignature: verify the signature (of hash and pubkey) --- lib.js | 78 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/lib.js b/lib.js index 2234db8..e0ac086 100644 --- a/lib.js +++ b/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.3.2 +(function (GLOBAL) { //lib v1.3.3 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -4349,15 +4349,6 @@ var bitjs = GLOBAL.bitjs = function () { }; - function ascii_to_hexa(str) { - var arr1 = []; - for (var n = 0, l = str.length; n < l; n++) { - var hex = Number(str.charCodeAt(n)).toString(16); - arr1.push(hex); - } - return arr1.join(''); - } - /* public vars */ bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23 bitjs.priv = 0xa3; //flochange - changed the prefix to FLO Mainnet Private key prefix 0xa3 @@ -4497,10 +4488,18 @@ return this.outputs.push(o); } + // flochange - Added fn to assign flodata to tx + btrx.addflodata = function (data) { + //checks for valid flo-data string + if (typeof data !== "string") + throw Error("floData should be String"); + if (data.length > 1040) + throw Error("floData Character Limit Exceeded"); + if (bitjs.strToBytes(data).some(c => c < 32 || c > 127)) + throw Error("floData contains Invalid characters (only ASCII characters allowed"); - btrx.addflodata = function (txcomments) { // flochange - this whole function needs to be done - this.floData = txcomments; - return this.floData; //flochange .. returning the txcomments -- check if the function return will assign + this.floData = data; + return this.floData; } @@ -4793,29 +4792,14 @@ } buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4)); - var flohex = ascii_to_hexa(this.floData); - var floDataCount = this.floData.length; - var floDataCountString; - //flochange -- creating unique data character count logic for floData. This string is prefixed before actual floData string in Raw Transaction - if (floDataCount < 16) { - floDataCountString = floDataCount.toString(16); - floDataCountString = "0" + floDataCountString; - } else if (floDataCount < 253) { - floDataCountString = floDataCount.toString(16); - } else if (floDataCount <= 1040) { - let floDataCountAdjusted = (floDataCount - 253) + parseInt("0xfd00fd"); - let floDataCountStringAdjusted = floDataCountAdjusted.toString(16); - floDataCountString = floDataCountStringAdjusted.substr(0, 2) + floDataCountStringAdjusted.substr(4, 2) + floDataCountStringAdjusted.substr(2, 2); - } else { - floDataCountString = "Character Limit Exceeded"; - } + //flochange -- append floData field + buffer = buffer.concat(bitjs.numToVarInt(this.floData.length)); + buffer = buffer.concat(bitjs.strToBytes(this.floData)) - return Crypto.util.bytesToHex(buffer) + floDataCountString + flohex; // flochange -- Addition of floDataCountString and floData in serialization + return Crypto.util.bytesToHex(buffer); } - - return btrx; } @@ -4856,6 +4840,36 @@ else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1)); } + //flochange - adding fn to convert string (for flodata) to byte + bitjs.strToBytes = function (str) { + return str.split('').map(c => c.charCodeAt(0)); + } + + /* decompress an compressed public key */ + bitjs.pubkeydecompress = function (pubkey) { + if ((typeof (pubkey) == 'string') && pubkey.match(/^[a-f0-9]+$/i)) { + var curve = EllipticCurve.getSECCurveByName("secp256k1"); + try { + var pt = curve.curve.decodePointHex(pubkey); + var x = pt.getX().toBigInteger(); + var y = pt.getY().toBigInteger(); + + var publicKeyBytes = EllipticCurve.integerToBytes(x, 32); + publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)); + publicKeyBytes.unshift(0x04); + return Crypto.util.bytesToHex(publicKeyBytes); + } catch (e) { + // console.log(e); + return false; + } + } + return false; + } + + bitjs.verifySignature = function (hash, sig, pubkey) { + return Bitcoin.ECDSA.verify(hash, sig, pubkey); + } + /* clone an object */ bitjs.clone = function (obj) { if (obj == null || typeof (obj) != 'object') return obj; From 1f670be8931806ecddf561dc24da40f157595291 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sun, 19 Feb 2023 17:43:07 +0530 Subject: [PATCH 2/7] lib v1.4.0: Support for FLO multisig address --- lib.js | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 202 insertions(+), 12 deletions(-) diff --git a/lib.js b/lib.js index e0ac086..2eeb2f7 100644 --- a/lib.js +++ b/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.3.3 +(function (GLOBAL) { //lib v1.4.0 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -4352,6 +4352,7 @@ /* public vars */ bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23 bitjs.priv = 0xa3; //flochange - changed the prefix to FLO Mainnet Private key prefix 0xa3 + bitjs.multisig = 0x5e; //flochange - prefix for FLO Mainnet Multisig 0x5e bitjs.compressed = false; /* provide a privkey and return an WIF */ @@ -4452,6 +4453,45 @@ return B58.encode(r.concat(checksum)); } + /* generate a multisig address from pubkeys and required signatures */ + bitjs.pubkeys2multisig = function (pubkeys, required) { + var s = []; + s.push(80 + required); //OP_1 + for (var i = 0; i < pubkeys.length; ++i) { + let bytes = Crypto.util.hexToBytes(pubkeys[i]); + s.push(bytes.length); + s = s.concat(bytes); + } + s.push(80 + pubkeys.length); //OP_1 + s.push(174); //OP_CHECKMULTISIG + + if (s.length > 520) { // too large + throw Error(`redeemScript size(=${s.length}) too large`) + } + + var x = ripemd160(Crypto.SHA256(s, { + asBytes: true + }), { + asBytes: true + }); + x.unshift(bitjs.multisig); + var r = x; + r = Crypto.SHA256(Crypto.SHA256(r, { + asBytes: true + }), { + asBytes: true + }); + var checksum = r.slice(0, 4); + var redeemScript = Crypto.util.bytesToHex(s); + var address = B58.encode(x.concat(checksum)); + + return { + 'address': address, + 'redeemScript': redeemScript, + 'size': s.length + }; + } + bitjs.transaction = function () { var btrx = {}; btrx.version = 2; //flochange look at this version @@ -4467,7 +4507,6 @@ 'hash': txid, 'index': index }; - //o.script = []; Signature and Public Key should be added after singning o.script = Crypto.util.hexToBytes(scriptPubKey); //push previous output pubkey script o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0); return this.inputs.push(o); @@ -4476,14 +4515,23 @@ btrx.addoutput = function (address, value) { var o = {}; var buf = []; - var addrDecoded = btrx.addressDecode(address); + var addr = this.addressDecode(address); o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10); - buf.push(118); //OP_DUP - buf.push(169); //OP_HASH160 - buf.push(addrDecoded.length); - buf = buf.concat(addrDecoded); // address in bytes - buf.push(136); //OP_EQUALVERIFY - buf.push(172); // OP_CHECKSIG + + if (addr.version === bitjs.pub) { // regular address + buf.push(118); //OP_DUP + buf.push(169); //OP_HASH160 + buf.push(addr.bytes.length); + buf = buf.concat(addr.bytes); // address in bytes + buf.push(136); //OP_EQUALVERIFY + buf.push(172); //OP_CHECKSIG + } else if (addr.version === bitjs.multisig) { // multisig address + buf.push(169); //OP_HASH160 + buf.push(addr.bytes.length); + buf = buf.concat(addr.bytes); // address in bytes + buf.push(135); //OP_EQUAL + } + o.script = buf; return this.outputs.push(o); } @@ -4503,7 +4551,7 @@ } - // Only standard addresses + // Only standard addresses (standard multisig supported) btrx.addressDecode = function (address) { var bytes = B58.decode(address); var front = bytes.slice(0, bytes.length - 4); @@ -4514,7 +4562,10 @@ asBytes: true }).slice(0, 4); if (checksum + "" == back + "") { - return front.slice(1); + return { + version: front[0], + bytes: front.slice(1) + }; } } @@ -4739,6 +4790,60 @@ return KBigInt; }; + btrx.parseScript = function (script) { + + var chunks = []; + var i = 0; + + function readChunk(n) { + chunks.push(script.slice(i, i + n)); + i += n; + }; + + while (i < script.length) { + var opcode = script[i++]; + if (opcode >= 0xF0) { + opcode = (opcode << 8) | script[i++]; + } + + var len; + if (opcode > 0 && opcode < 76) { //OP_PUSHDATA1 + readChunk(opcode); + } else if (opcode == 76) { //OP_PUSHDATA1 + len = script[i++]; + readChunk(len); + } else if (opcode == 77) { //OP_PUSHDATA2 + len = (script[i++] << 8) | script[i++]; + readChunk(len); + } else if (opcode == 78) { //OP_PUSHDATA4 + len = (script[i++] << 24) | (script[i++] << 16) | (script[i++] << 8) | script[i++]; + readChunk(len); + } else { + chunks.push(opcode); + } + + if (i < 0x00) { + break; + } + } + + return chunks; + } + + btrx.decodeRedeemScript = function (rs) { + if (typeof rs == "string") + rs = Crypto.util.hexToBytes(rs); + var script = this.parseScript(rs); + var r = {}; + r.required = script[0] - 80; + r.pubkeys = []; + for (var i = 1; i < script.length - 2; i++) + r.pubkeys.push(Crypto.util.bytesToHex(script[i])); + r.address = bitjs.pubkeys2multisig(r.pubkeys, r.required).address; + r.redeemscript = Crypto.util.bytesToHex(rs); + return r; + } + /* sign a "standard" input */ btrx.signinput = function (index, wif, sigHashType) { var key = bitjs.wif2pubkey(wif); @@ -4755,15 +4860,100 @@ return true; } + /* sign a multisig input */ + btrx.signmultisig = function (index, wif, sigHashType) { + + var script = Array.from(this.inputs[index].script); + var redeemScript, sigsList = []; + + if (script[0] == 0) { //script with signatures + script = this.parseScript(script); + for (var i = 0; i < script.length; i++) { + if (Array.isArray(script[i])) { + if (script[i][0] == 48) //0x30 DERSequence + sigsList.push(script[i]); + else if (script[i][0] >= 80 && script[i][script[i].length - 1] == 174) //OP_CHECKMULTISIG + redeemScript = script[i]; + } + } + } else { //script = redeemscript + redeemScript = script; + } + + var pubkeyList = this.decodeRedeemScript(redeemScript).pubkeys; + var pubkey = bitjs.wif2pubkey(wif)['pubkey']; + if (!pubkeyList.includes(pubkey)) //wif not a part of this multisig + return false; + + pubkeyList = pubkeyList.map(pub => Crypto.util.hexToBytes(bitjs.pubkeydecompress(pub))); //decompress pubkeys + + var shType = sigHashType || 1; + this.inputs[index].script = redeemScript; //script to be signed is redeemscript + var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType)); + sigsList.push(signature); + + var buf = []; + buf.push(0); + + //verify signatures and order them (also remove duplicate sigs) + for (let x in pubkeyList) { + for (let y in sigsList) { + var sighash = Crypto.util.hexToBytes(this.transactionHash(index, sigsList[y].slice(-1)[0] * 1)); + if (bitjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) { + buf.push(sigsList[y].length); + buf = buf.concat(sigsList[y]); + break; //ensures duplicate sigs from same pubkey are not added + } + } + } + + //append redeemscript + buf.push(redeemScript.length); + buf = buf.concat(redeemScript); + + this.inputs[index].script = buf; + return true; + } + /* sign inputs */ btrx.sign = function (wif, sigHashType) { var shType = sigHashType || 1; for (var i = 0; i < this.inputs.length; i++) { - this.signinput(i, wif, shType); + + var decodedScript = this.scriptDecode(i); + + if (decodedScript.type == "scriptpubkey" && decodedScript.signed == false) { //regular + var addr = bitjs.wif2address(wif)["address"];; + if (decodedScript.pubhash == Crypto.util.bytesToHex(this.addressDecode(addr).bytes)) //input belongs to wif + this.signinput(i, wif, shType); + } else if (decodedScript.type == "multisig") { //multisig + this.signmultisig(i, wif, shType); + } } return this.serialize(); } + // function to find type of the script in input + btrx.scriptDecode = function (index) { + var script = this.parseScript(this.inputs[index].script); + if (script.length == 5 && script[script.length - 1] == 172) { + //OP_DUP OP_HASH160 [address bytes] OP_EQUALVERIFY OP_CHECKSIG + // regular scriptPubkey (not signed) + return { type: 'scriptpubkey', signed: false, pubhash: Crypto.util.bytesToHex(script[2]) }; + } else if (script.length == 2 && script[0][0] == 48) { + //[signature] [pubkey] + //(probably) regular signed + return { type: 'scriptpubkey', signed: true }; + } else if (script[0] == 0 && script[script.length - 1][script[script.length - 1].length - 1] == 174) { + //0 [signatues] [redeemscript OP_CHECKMULTISIG] + // multisig with signature + return { type: 'multisig', rs: script[script.length - 1] }; + } else if (script[0] >= 80 && script[script.length - 1] == 174) { + //redeemscript: 80+ [pubkeys] OP_CHECKMULTISIG + // multisig without signature + return { type: 'multisig', rs: Array.from(this.inputs[index].script) }; + } + } /* serialize a transaction */ btrx.serialize = function () { From 0031761a066dd27b3f2992a9a9670996845e3173 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 20 Feb 2023 02:58:47 +0530 Subject: [PATCH 3/7] lib v1.4.1: Bitcoin.Address: FLO-multisig support - Bitcoin.Address now supports multisg version - new Bitcoin.Address (hex) will now assign the respective version when its regular or multisig address - Added constants: Bitcoin.Address .standardVersion= 0x23 .multisigVersion = 0x5e .testnetVersion = 0x73 --- lib.js | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/lib.js b/lib.js index 2eeb2f7..550402f 100644 --- a/lib.js +++ b/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.4.0 +(function (GLOBAL) { //lib v1.4.1 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -5224,17 +5224,26 @@ //https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js Bitcoin.Address = function (bytes) { - if (GLOBAL.cryptocoin == "FLO") - this.version = 0x23; // FLO mainnet public address - else if (GLOBAL.cryptocoin == "FLO_TEST") - this.version = 0x73; // FLO testnet public address if ("string" == typeof bytes) { - bytes = Bitcoin.Address.decodeString(bytes, this.version); + var d = Bitcoin.Address.decodeString(bytes); + bytes = d.hash; + if (GLOBAL.cryptocoin == "FLO" && (d.version == Bitcoin.Address.standardVersion || d.version == Bitcoin.Address.multisigVersion)) + this.version = d.version; + else if (GLOBAL.cryptocoin == "FLO_TEST" && d.version == Bitcoin.Address.testnetVersion) + this.version = d.version; + else throw "Version (prefix) " + d.version + " not supported!"; + } else { + if (GLOBAL.cryptocoin == "FLO") + this.version = Bitcoin.Address.standardVersion; + else if (GLOBAL.cryptocoin == "FLO_TEST") + this.version = Bitcoin.Address.testnetVersion; // FLO testnet public address } this.hash = bytes; }; - Bitcoin.Address.networkVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) // *this has no effect * + Bitcoin.Address.standardVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) + Bitcoin.Address.multisigVersion = 0x5e; // (FLO multisig 0x5e, 94D) + Bitcoin.Address.testnetVersion = 0x73; // (FLO testnet 0x73, 115D) /** * Serialize this object as a standard Bitcoin address. @@ -5263,7 +5272,7 @@ /** * Parse a Bitcoin address contained in a string. */ - Bitcoin.Address.decodeString = function (string, version) { + Bitcoin.Address.decodeString = function (string) { var bytes = Bitcoin.Base58.decode(string); var hash = bytes.slice(0, 21); var checksum = Crypto.SHA256(Crypto.SHA256(hash, { @@ -5279,11 +5288,12 @@ throw "Checksum validation failed!"; } - if (version != hash.shift()) { + /*if (version != hash.shift()) { throw "Version " + hash.shift() + " not supported!"; - } + }*/ - return hash; + var version = hash.shift(); + return { version, hash }; }; //https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js Bitcoin.ECDSA = (function () { From dac87a85958f8e01e27f8ae9c83c99842edc35bb Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 20 Feb 2023 03:10:15 +0530 Subject: [PATCH 4/7] floCrypto v2.3.4: multisig address - Added getMultisigAddress(pubkeys, required_sigs): returns a multisig address from given pubkey list and required signatures - Added decodeRedeemScript (redeemScript): decodes the given redeemscript (of multisig) - Updated validateFloID: now validates FLO regular and multisig address. optional argument regularOnly(default=false) if true validates only FLO regular address. --- floCrypto.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/floCrypto.js b/floCrypto.js index 563d77f..5e8376f 100644 --- a/floCrypto.js +++ b/floCrypto.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCrypto v2.3.3e +(function (EXPORTS) { //floCrypto v2.3.4 /* FLO Crypto Operators */ 'use strict'; const floCrypto = EXPORTS; @@ -231,12 +231,36 @@ } } + floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) { + if (!Array.isArray(publicKeyList) || !publicKeyList.length) + return null; + if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1) + return null; + try { + var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures); + return multisig; + } catch { + return null; + } + } + + floCrypto.decodeRedeemScript = function (redeemScript) { + try { + var decoded = bitjs.transaction().decodeRedeemScript(redeemScript); + return decoded; + } catch { + return null; + } + } + //Check if the given flo-id is valid or not - floCrypto.validateFloID = function (floID) { + floCrypto.validateFloID = function (floID, regularOnly = false) { if (!floID) return false; try { let addr = new Bitcoin.Address(floID); + if (regularOnly && addr.version != Bitcoin.Address.standardVersion) + return false; return true; } catch { return false; From 2e0846edc420f3f2fec2c6faaf413e0704227f12 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 20 Feb 2023 04:59:23 +0530 Subject: [PATCH 5/7] floBlockchainAPI v2.4.0: multisig tx Changes: - multisig addresses are accepted as receiver address in all send-tx fns - multisig addresses are accepted in query fns - sendTx: sender can be regular address only Added 3 multisig transaction fns: - createMultisigTx: creates unsigned tx for multisig sender - sendMultisigTx: create signed multisig tx and broadcast it - writeMultisigData: same as writeData(), but for multisig --- floBlockchainAPI.js | 133 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 8 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index ab778fe..d4f9bf6 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.3.3e +(function (EXPORTS) { //floBlockchainAPI v2.4.0 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -126,7 +126,7 @@ return new Promise((resolve, reject) => { if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); - else if (!floCrypto.validateFloID(senderAddr)) + else if (!floCrypto.validateFloID(senderAddr, true)) return reject(`Invalid address : ${senderAddr}`); else if (!floCrypto.validateFloID(receiverAddr)) return reject(`Invalid address : ${receiverAddr}`); @@ -203,7 +203,7 @@ //merge all UTXOs of a given floID into a single UTXO floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') { return new Promise((resolve, reject) => { - if (!floCrypto.validateFloID(floID)) + if (!floCrypto.validateFloID(floID, true)) return reject(`Invalid floID`); if (!floCrypto.verifyPrivKey(privKey, floID)) return reject("Invalid Private Key"); @@ -381,7 +381,6 @@ for (let floID in senders) promises.push(promisedAPI(`api/addr/${floID}/utxo`)); Promise.all(promises).then(results => { - let wifSeq = []; var trx = bitjs.transaction(); for (let floID in senders) { let utxos = results.shift(); @@ -391,13 +390,11 @@ sendAmt = totalSendAmt * ratio; } else sendAmt = senders[floID].coins + dividedFee; - let wif = senders[floID].wif; let utxoAmt = 0.0; for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < sendAmt); i--) { if (utxos[i].confirmations) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); - wifSeq.push(wif); utxoAmt += utxos[i].amount; } } @@ -410,8 +407,8 @@ for (let floID in receivers) trx.addoutput(floID, receivers[floID]); trx.addflodata(floData.replace(/\n/g, ' ')); - for (let i = 0; i < wifSeq.length; i++) - trx.signinput(i, wifSeq[i], 1); + for (let floID in senders) + trx.sign(senders[floID].wif, 1); var signedTxHash = trx.serialize(); broadcastTx(signedTxHash) .then(txid => resolve(txid)) @@ -421,6 +418,126 @@ }) } + //Create a multisig transaction + const createMultisigTx = floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + var multisig = floCrypto.decodeRedeemScript(redeemScript); + + //validate multisig script and flodata + if (!multisig) + return reject(`Invalid redeemScript`); + var senderAddr = multisig.address; + if (!floCrypto.validateFloID(senderAddr)) + return reject(`Invalid multisig : ${senderAddr}`); + else if (!floCrypto.validateASCII(floData)) + return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); + //validate receiver addresses + if (!Array.isArray(receivers)) + receivers = [receivers]; + for (let r of receivers) + if (!floCrypto.validateFloID(r)) + return reject(`Invalid address : ${r}`); + //validate amounts + if (!Array.isArray(amounts)) + amounts = [amounts]; + if (amounts.length != receivers.length) + return reject("Receivers and amounts have different length"); + var sendAmt = 0; + for (let a of amounts) { + if (typeof a !== 'number' || a <= 0) + return reject(`Invalid amount : ${a}`); + sendAmt += a; + } + + getBalance(senderAddr).then(balance => { + var fee = DEFAULT.fee; + if (balance < sendAmt + fee) + return reject("Insufficient FLO balance!"); + //get unconfirmed tx list + promisedAPI(`api/addr/${senderAddr}`).then(result => { + readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => { + let unconfirmedSpent = {}; + for (let tx of result.items) + if (tx.confirmations == 0) + for (let vin of tx.vin) + if (vin.addr === senderAddr) { + if (Array.isArray(unconfirmedSpent[vin.txid])) + unconfirmedSpent[vin.txid].push(vin.vout); + else + unconfirmedSpent[vin.txid] = [vin.vout]; + } + //get utxos list + promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => { + //form/construct the transaction data + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + for (var i = utxos.length - 1; + (i >= 0) && (utxoAmt < sendAmt + fee); i--) { + //use only utxos with confirmations (strict_utxo mode) + if (utxos[i].confirmations || !strict_utxo) { + if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout)) + continue; //A transaction has already used the utxo, but is unconfirmed. + trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript + utxoAmt += utxos[i].amount; + }; + } + if (utxoAmt < sendAmt + fee) + reject("Insufficient FLO: Some UTXOs are unconfirmed"); + else { + for (let i in receivers) + trx.addoutput(receivers[i], amounts[i]); + var change = utxoAmt - sendAmt - fee; + if (change > DEFAULT.minChangeAmt) + trx.addoutput(senderAddr, change); + trx.addflodata(floData.replace(/\n/g, ' ')); + resolve(trx); + } + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }).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) => { + var multisig = floCrypto.decodeRedeemScript(redeemScript); + if (!multisig) + return reject(`Invalid redeemScript`); + if (privateKeys.length < multisig.required) + return reject(`Insufficient privateKeys (required ${multisig.required})`); + for (let pk of privateKeys) { + var flag = false; + for (let pub of multisig.pubkeys) + if (floCrypto.verifyPrivKey(pk, pub, false)) + flag = true; + if (!flag) + return reject(`Invalid Private key`); + } + createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo).then(trx => { + for (let pk of privateKeys) + trx.sign(pk, 1); + var signedTxHash = trx.serialize(); + broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + + floBlockchainAPI.writeMultisigData = function (redeemScript, data, privatekeys, receiverAddr = DEFAULT.receiverID, options = {}) { + let strict_utxo = options.strict_utxo === false ? false : true, + sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt; + return new Promise((resolve, reject) => { + if (!floCrypto.validateFloID(receiverAddr)) + return reject(`Invalid receiver: ${receiverAddr}`); + sendMultisigTx(redeemScript, privatekeys, receiverAddr, sendAmt, data, strict_utxo) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }) + } + //Broadcast signed Tx in blockchain using API const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { return new Promise((resolve, reject) => { From a102f5225b20c9a12aad2d2271d029d0e512523b Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 20 Feb 2023 05:02:43 +0530 Subject: [PATCH 6/7] floCrypto v2.3.4a: bug fix - Fixed: verifyPrivKey incorrectly returning false when pubkey is lowercase. (pubkey hex is case-insensitive) --- floCrypto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/floCrypto.js b/floCrypto.js index 5e8376f..19ffedc 100644 --- a/floCrypto.js +++ b/floCrypto.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCrypto v2.3.4 +(function (EXPORTS) { //floCrypto v2.3.4a /* FLO Crypto Operators */ 'use strict'; const floCrypto = EXPORTS; @@ -222,7 +222,7 @@ key.setCompressed(true); if (isfloID && pubKey_floID == key.getBitcoinAddress()) return true; - else if (!isfloID && pubKey_floID == key.getPubKeyHex()) + else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase()) return true; else return false; From 9132d169d247e9e5ae7a3c56613ad378d5a2d58a Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 20 Feb 2023 05:20:04 +0530 Subject: [PATCH 7/7] lib v1.4.1a: bug fix - Fixed: bitjs.btrx.decodeRedeemScript not throwing Invalid RedeemScript error --- lib.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib.js b/lib.js index 550402f..d0871b1 100644 --- a/lib.js +++ b/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.4.1 +(function (GLOBAL) { //lib v1.4.1a 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -4834,6 +4834,8 @@ if (typeof rs == "string") rs = Crypto.util.hexToBytes(rs); var script = this.parseScript(rs); + if (!(script[0] > 80 && script[script.length - 2] > 80 && script[script.length - 1] == 174)) //OP_CHECKMULTISIG + throw "Invalid RedeemScript"; var r = {}; r.required = script[0] - 80; r.pubkeys = [];