From ce4bdcd83c0ad07b675a82c3e96227b7849071ed Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 25 Feb 2023 15:16:46 +0530 Subject: [PATCH 1/3] Update lib.js --- scripts/lib.js | 143 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 24 deletions(-) diff --git a/scripts/lib.js b/scripts/lib.js index d0871b1..e383403 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.4.1a +(function (GLOBAL) { //lib v1.4.2b 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -4355,6 +4355,12 @@ bitjs.multisig = 0x5e; //flochange - prefix for FLO Mainnet Multisig 0x5e bitjs.compressed = false; + if (GLOBAL.cryptocoin == 'FLO_TEST') { + bitjs.pub = 0x73; // flochange - changed the prefix to FLO TestNet PublicKey Prefix 0x73 + bitjs.priv = 0xa3; //flochange - changed the prefix to FLO TestNet Private key prefix 0xa3 + bitjs.multisig = 0xc6; //flochange - prefix for FLO TestNet Multisig 0xc6 + } + /* provide a privkey and return an WIF */ bitjs.privkey2wif = function (h) { var r = Crypto.util.hexToBytes(h); @@ -4492,7 +4498,7 @@ }; } - bitjs.transaction = function () { + bitjs.transaction = function (tx_data = undefined) { var btrx = {}; btrx.version = 2; //flochange look at this version btrx.inputs = []; @@ -4521,14 +4527,12 @@ 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 = this.writeBytesToScriptBuffer(buf, 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 = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes buf.push(135); //OP_EQUAL } @@ -4790,6 +4794,27 @@ return KBigInt; }; + btrx.writeBytesToScriptBuffer = function (buf, bytes) { + if (bytes.length < 76) { //OP_PUSHDATA1 + buf.push(bytes.length); + } else if (bytes.length <= 0xff) { + buf.push(76); //OP_PUSHDATA1 + buf.push(bytes.length); + } else if (bytes.length <= 0xffff) { + buf.push(77); //OP_PUSHDATA2 + buf.push(bytes.length & 0xff); + buf.push((bytes.length >>> 8) & 0xff); + } else { + buf.push(78); //OP_PUSHDATA4 + buf.push(bytes.length & 0xff); + buf.push((bytes.length >>> 8) & 0xff); + buf.push((bytes.length >>> 16) & 0xff); + buf.push((bytes.length >>> 24) & 0xff); + } + buf = buf.concat(bytes); + return buf; + } + btrx.parseScript = function (script) { var chunks = []; @@ -4853,8 +4878,7 @@ var signature = this.transactionSig(index, wif, shType); var buf = []; var sigBytes = Crypto.util.hexToBytes(signature); - buf.push(sigBytes.length); - buf = buf.concat(sigBytes); + buf = this.writeBytesToScriptBuffer(buf, sigBytes); var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']); buf.push(pubKeyBytes.length); buf = buf.concat(pubKeyBytes); @@ -4902,16 +4926,14 @@ 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]); + buf = this.writeBytesToScriptBuffer(buf, sigsList[y]); break; //ensures duplicate sigs from same pubkey are not added } } } //append redeemscript - buf.push(redeemScript.length); - buf = buf.concat(redeemScript); + buf = this.writeBytesToScriptBuffer(buf, redeemScript); this.inputs[index].script = buf; return true; @@ -4992,6 +5014,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; } @@ -5229,23 +5323,22 @@ if ("string" == typeof bytes) { 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) + if (d.version == Bitcoin.Address.standardVersion || d.version == Bitcoin.Address.multisigVersion) 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.version = Bitcoin.Address.standardVersion; } this.hash = bytes; }; 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) + + if (GLOBAL.cryptocoin == "FLO_TEST") { + Bitcoin.Address.standardVersion = 0x73; // (FLO testnet 0x73, 115D), (Bitcoin Mainnet, 0x00, 0D) + Bitcoin.Address.multisigVersion = 0xc6; // (FLO testnet multisig 0xc6, 198D) + } /** * Serialize this object as a standard Bitcoin address. @@ -6704,6 +6797,7 @@ return { 'address': address, 'redeemScript': r.redeemScript, + 'scripthash': Crypto.util.bytesToHex(program), 'size': r.size }; } @@ -6797,15 +6891,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 +7898,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 b88a713c803580110230c36555dd4c1e231b9ddb Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 25 Feb 2023 15:16:49 +0530 Subject: [PATCH 2/3] Update floCrypto.js --- scripts/floCrypto.js | 58 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/scripts/floCrypto.js b/scripts/floCrypto.js index 19ffedc..ec41da4 100644 --- a/scripts/floCrypto.js +++ b/scripts/floCrypto.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCrypto v2.3.4a +(function (EXPORTS) { //floCrypto v2.3.5a /* FLO Crypto Operators */ 'use strict'; const floCrypto = EXPORTS; @@ -234,7 +234,7 @@ floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) { if (!Array.isArray(publicKeyList) || !publicKeyList.length) return null; - if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1) + if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length) return null; try { var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures); @@ -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 4078d24487c9c1754c037bee8a0805a5048a5f6e Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 25 Feb 2023 15:16:51 +0530 Subject: [PATCH 3/3] Update floBlockchainAPI.js --- scripts/floBlockchainAPI.js | 193 ++++++++++++++++++++++++++++++++++-- 1 file changed, 183 insertions(+), 10 deletions(-) diff --git a/scripts/floBlockchainAPI.js b/scripts/floBlockchainAPI.js index d4f9bf6..35f1e0a 100644 --- a/scripts/floBlockchainAPI.js +++ b/scripts/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.4.0 +(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, @@ -121,8 +128,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 +137,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 +180,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 +445,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 +525,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 +573,144 @@ }) } + 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 + tx = deserializeTx(tx); + var signedTxHex = tx.sign(privateKey, sighashtype); + return signedTxHex; + } + + const checkSigned = 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); + } + + 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) => {