Merge pull request #9 from ranchimall/dev

flosight changes
This commit is contained in:
Sai Raj 2023-05-19 01:24:36 +05:30 committed by GitHub
commit fe424d56dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 2209 additions and 919 deletions

View File

@ -2,7 +2,7 @@
CREATE TABLE LastTx( CREATE TABLE LastTx(
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
num INT, txid VARCHAR(128),
PRIMARY KEY(floID) PRIMARY KEY(floID)
); );

View File

@ -26,9 +26,9 @@ function listTables() {
function checksumTable(table) { function checksumTable(table) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("CHECKSUM TABLE " + table).then(result => { DB.query("CHECKSUM TABLE ??", [table]).then(result => {
let checksum = result[0].Checksum; let checksum = result[0].Checksum;
DB.query("SELECT COUNT(*) AS rec_count FROM " + table) DB.query("SELECT COUNT(*) AS rec_count FROM ??", [table])
.then(result => resolve({ table, rec_count: result[0].rec_count, checksum })) .then(result => resolve({ table, rec_count: result[0].rec_count, checksum }))
.catch(error => reject(error)) .catch(error => reject(error))
}).catch(error => reject(error)) }).catch(error => reject(error))

View File

@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RanchiMall exchange</title> <title>RanchiMall Exchange</title>
<meta name="description" content="Trade FLO and FLO based RanchiMall tokens."> <meta name="description" content="Trade FLO and FLO based RanchiMall tokens.">
<meta name="theme-color" content="#516beb" /> <meta name="theme-color" content="#516beb" />
<script src="scripts/components.js" defer></script> <script src="scripts/components.js" defer></script>
@ -1703,7 +1703,7 @@
// code if page is hidden // code if page is hidden
} else { } else {
if (floGlobals.exchangeApiLoaded) if (floGlobals.exchangeApiLoaded)
updateRate(); updateRate().catch(err => console.log(err))
} }
} }
@ -1808,7 +1808,7 @@
return listedAssets.hasOwnProperty(asset) ? listedAssets[asset].icon : listedAssets.default.icon; return listedAssets.hasOwnProperty(asset) ? listedAssets[asset].icon : listedAssets.default.icon;
} }
function formatAmount(amount, shorten = false) { function formatAmount(amount = 0, shorten = false) {
return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR', maximumFractionDigits: shorten ? 2 : 8 }) return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR', maximumFractionDigits: shorten ? 2 : 8 })
} }
// Convert milliseconds to time left in HH:MM:SS format // Convert milliseconds to time left in HH:MM:SS format
@ -1821,9 +1821,14 @@
// remove digitals after specified decimal places without rounding // remove digitals after specified decimal places without rounding
function toFixed(num, fixed = 8) { function toFixed(num, fixed = 8) {
var re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?'); const re = new RegExp(`^-?\\d+(?:\\.\\d{0,${fixed}})?`);
return parseFloat(num.toString().match(re)[0]); const match = num.toString().match(re);
if (!match) {
return NaN;
} }
return parseFloat(match[0]);
}
function getSuggestedPrice(asset = pagesData.params.asset || 'FLO') { function getSuggestedPrice(asset = pagesData.params.asset || 'FLO') {
return toFixed(parseFloat(floGlobals.exchangeRates[asset]) * deviation[tradeType]) return toFixed(parseFloat(floGlobals.exchangeRates[asset]) * deviation[tradeType])
@ -1884,7 +1889,7 @@
clone.querySelector('.listed-asset__countdown').style.setProperty('--path-length', pathLength) clone.querySelector('.listed-asset__countdown').style.setProperty('--path-length', pathLength)
}, 1000); }, 1000);
floGlobals.countDowns.timeouts[asset] = setTimeout(() => { floGlobals.countDowns.timeouts[asset] = setTimeout(() => {
updateRate() updateRate().catch(console.error)
delete floGlobals.countDowns.timeouts[asset] delete floGlobals.countDowns.timeouts[asset]
}, countDown - Date.now()); }, countDown - Date.now());
return clone return clone
@ -1976,8 +1981,10 @@
new ResizeObserver(entries => { new ResizeObserver(entries => {
chartDimensions.height = entries[0].contentRect.height - 10; chartDimensions.height = entries[0].contentRect.height - 10;
chartDimensions.width = entries[0].contentRect.width; chartDimensions.width = entries[0].contentRect.width;
if (chart) if (chart) {
if (chartDimensions.width <= 0 || chartDimensions.height <= 0) return;
chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height }); chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height });
}
}).observe(getRef('price_chart_wrapper')); }).observe(getRef('price_chart_wrapper'));
}) })
}, },
@ -2118,6 +2125,7 @@
const chartObserver = new IntersectionObserver(entries => { const chartObserver = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && chart) { if (entries[0].isIntersecting && chart) {
const { width, height } = entries[0].target.getBoundingClientRect() const { width, height } = entries[0].target.getBoundingClientRect()
if (width <= 0 || height <= 0 || chartDimensions.height === height - 10 && chartDimensions.width === width) return
chartDimensions.height = height - 10; chartDimensions.height = height - 10;
chartDimensions.width = width; chartDimensions.width = width;
chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height }); chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height });
@ -2763,6 +2771,7 @@
showSuggestedPrice() showSuggestedPrice()
}).catch(error => { }).catch(error => {
notify(error.message, 'error'); notify(error.message, 'error');
reject(error)
}) })
}).catch(error => console.error(error)) }).catch(error => console.error(error))
} }
@ -2793,7 +2802,7 @@
if (refreshButton) if (refreshButton)
buttonLoader('portfolio_balance_refresh_button', false); buttonLoader('portfolio_balance_refresh_button', false);
} }
}) }).catch(error => console.error(error))
} }
let accountDetails = {} let accountDetails = {}

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //btcOperator v1.1.1 (function (EXPORTS) { //btcOperator v1.1.2a
/* BTC Crypto and API Operator */ /* BTC Crypto and API Operator */
const btcOperator = EXPORTS; const btcOperator = EXPORTS;
@ -130,50 +130,84 @@
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired); 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) //convert from one blockchain to another blockchain (target version)
btcOperator.convert = {}; btcOperator.convert = {};
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) { 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)) if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
return null; return null;
else else
return encodeLegacy(keyHex, target_version); return util.encodeLegacy(keyHex, target_version);
} }
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) { 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) if (!rawHex)
return null; return null;
else 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) { 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) if (!rawHex)
return null; return null;
else 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) { 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) if (!rawHex)
return null; return null;
else 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) { 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) if (!rawHex)
return null; return null;
else 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 decode = coinjs.base58decode(source);
var raw = decode.slice(0, decode.length - 4), var raw = decode.slice(0, decode.length - 4),
checksum = decode.slice(decode.length - 4); checksum = decode.slice(decode.length - 4);
@ -183,7 +217,7 @@
asBytes: true asBytes: true
}); });
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) 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(); let version = raw.shift();
return { return {
version: version, version: version,
@ -191,7 +225,7 @@
} }
} }
function encodeLegacy(hex, version) { util.encodeLegacy = function (hex, version) {
var bytes = Crypto.util.hexToBytes(hex); var bytes = Crypto.util.hexToBytes(hex);
bytes.unshift(version); bytes.unshift(version);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, { var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
@ -203,10 +237,10 @@
return coinjs.base58encode(bytes.concat(checksum)); return coinjs.base58encode(bytes.concat(checksum));
} }
function decodeBech32(source) { util.decodeBech32 = function (source) {
let decode = coinjs.bech32_decode(source); let decode = coinjs.bech32_decode(source);
if (!decode) if (!decode)
return null; return false;
var raw = decode.data; var raw = decode.data;
let version = raw.shift(); let version = raw.shift();
raw = coinjs.bech32_convert(raw, 5, 8, false); 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); var bytes = Crypto.util.hexToBytes(hex);
bytes = coinjs.bech32_convert(bytes, 8, 5, true); bytes = coinjs.bech32_convert(bytes, 8, 5, true);
bytes.unshift(version) bytes.unshift(version)
@ -669,12 +703,15 @@
btcOperator.checkIfSameTx = function (tx1, tx2) { btcOperator.checkIfSameTx = function (tx1, tx2) {
tx1 = deserializeTx(tx1); tx1 = deserializeTx(tx1);
tx2 = deserializeTx(tx2); tx2 = deserializeTx(tx2);
//compare input and output length
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length) if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
return false; return false;
//compare inputs
for (let i = 0; i < tx1.ins.length; i++) 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) if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
return false; 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)) 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 false;
return true; return true;
@ -706,13 +743,13 @@
var address; var address;
switch (out.script.chunks[0]) { switch (out.script.chunks[0]) {
case 0: //bech32, multisig-bech32 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; break;
case 169: //segwit, multisig-segwit 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; break;
case 118: //legacy 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 { return {
address, address,

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floBlockchainAPI v2.3.3d (function (EXPORTS) { //floBlockchainAPI v2.5.6b
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
'use strict'; 'use strict';
const floBlockchainAPI = EXPORTS; const floBlockchainAPI = EXPORTS;
@ -6,15 +6,24 @@
const DEFAULT = { const DEFAULT = {
blockchain: floGlobals.blockchain, blockchain: floGlobals.blockchain,
apiURL: { apiURL: {
FLO: ['https://flosight.duckdns.org/'], FLO: ['https://flosight.ranchimall.net/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] FLO_TEST: ['https://flosight-testnet.ranchimall.net/']
}, },
sendAmt: 0.001, sendAmt: 0.0003,
fee: 0.0005, fee: 0.0002,
minChangeAmt: 0.0005, minChangeAmt: 0.0002,
receiverID: floGlobals.adminID receiverID: floGlobals.adminID
}; };
const SATOSHI_IN_BTC = 1e8;
const isUndefined = val => typeof val === 'undefined';
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);
util.toFixed = value => parseFloat((value).toFixed(8));
Object.defineProperties(floBlockchainAPI, { Object.defineProperties(floBlockchainAPI, {
sendAmt: { sendAmt: {
get: () => DEFAULT.sendAmt, get: () => DEFAULT.sendAmt,
@ -103,9 +112,11 @@
}); });
//Promised function to get data from API //Promised function to get data from API
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) { const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall, query_params = undefined) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
//console.log(apicall); if (!isUndefined(query_params))
apicall += '?' + new URLSearchParams(JSON.parse(JSON.stringify(query_params))).toString();
//console.debug(apicall);
fetch_api(apicall) fetch_api(apicall)
.then(result => resolve(result)) .then(result => resolve(result))
.catch(error => reject(error)); .catch(error => reject(error));
@ -113,25 +124,55 @@
} }
//Get balance for the given Address //Get balance for the given Address
const getBalance = floBlockchainAPI.getBalance = function (addr) { const getBalance = floBlockchainAPI.getBalance = function (addr, after = null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addr/${addr}/balance`) let api = `api/addr/${addr}/balance`, query_params = {};
.then(balance => resolve(parseFloat(balance))) if (after) {
.catch(error => reject(error)); if (typeof after === 'string' && /^[0-9a-z]{64}$/i.test(after))
query_params.after = after;
else return reject("Invalid 'after' parameter");
}
promisedAPI(api, query_params).then(result => {
if (typeof result === 'object' && result.lastItem) {
getBalance(addr, result.lastItem)
.then(r => resolve(util.toFixed(r + result.data)))
.catch(error => reject(error))
} else resolve(result);
}).catch(error => reject(error))
}); });
} }
//Send Tx to blockchain const getUTXOs = address => new Promise((resolve, reject) => {
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { promisedAPI(`api/addr/${address}/utxo`)
.then(utxo => resolve(utxo))
.catch(error => reject(error))
})
const getUnconfirmedSpent = address => new Promise((resolve, reject) => {
readTxs(address, { mempool: "only" }).then(result => {
let unconfirmedSpent = {};
for (let tx of result.items)
if (tx.confirmations == 0)
for (let vin of tx.vin)
if (vin.addr === address) {
if (Array.isArray(unconfirmedSpent[vin.txid]))
unconfirmedSpent[vin.txid].push(vin.vout);
else
unconfirmedSpent[vin.txid] = [vin.vout];
}
resolve(unconfirmedSpent);
}).catch(error => reject(error))
})
//create a transaction with single sender
const createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData)) if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); 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}`); return reject(`Invalid address : ${senderAddr}`);
else if (!floCrypto.validateFloID(receiverAddr)) else if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid address : ${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) else if (typeof sendAmt !== 'number' || sendAmt <= 0)
return reject(`Invalid sendAmt : ${sendAmt}`); return reject(`Invalid sendAmt : ${sendAmt}`);
@ -139,21 +180,8 @@
var fee = DEFAULT.fee; var fee = DEFAULT.fee;
if (balance < sendAmt + fee) if (balance < sendAmt + fee)
return reject("Insufficient FLO balance!"); return reject("Insufficient FLO balance!");
//get unconfirmed tx list getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
promisedAPI(`api/addr/${senderAddr}`).then(result => { getUTXOs(senderAddr).then(utxos => {
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 //form/construct the transaction data
var trx = bitjs.transaction(); var trx = bitjs.transaction();
var utxoAmt = 0.0; var utxoAmt = 0.0;
@ -175,14 +203,34 @@
if (change > DEFAULT.minChangeAmt) if (change > DEFAULT.minChangeAmt)
trx.addoutput(senderAddr, change); trx.addoutput(senderAddr, change);
trx.addflodata(floData.replace(/\n/g, ' ')); trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1); resolve(trx);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
} }
}).catch(error => reject(error)) }).catch(error => reject(error))
}).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)) }).catch(error => reject(error))
}); });
} }
@ -203,7 +251,7 @@
//merge all UTXOs of a given floID into a single UTXO //merge all UTXOs of a given floID into a single UTXO
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') { floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(floID)) if (!floCrypto.validateFloID(floID, true))
return reject(`Invalid floID`); return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID)) if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key"); return reject("Invalid Private Key");
@ -212,7 +260,7 @@
var trx = bitjs.transaction(); var trx = bitjs.transaction();
var utxoAmt = 0.0; var utxoAmt = 0.0;
var fee = DEFAULT.fee; var fee = DEFAULT.fee;
promisedAPI(`api/addr/${floID}/utxo`).then(utxos => { getUTXOs(floID).then(utxos => {
for (var i = utxos.length - 1; i >= 0; i--) for (var i = utxos.length - 1; i >= 0; i--)
if (utxos[i].confirmations) { if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
@ -228,6 +276,56 @@
}) })
} }
//split sufficient UTXOs of a given floID for a parallel sending
floBlockchainAPI.splitUTXOs = function (floID, privKey, count, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(floID, true))
return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key");
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
var fee = DEFAULT.fee;
var splitAmt = DEFAULT.sendAmt + fee;
var totalAmt = splitAmt * count;
getBalance(floID).then(balance => {
var fee = DEFAULT.fee;
if (balance < totalAmt + fee)
return reject("Insufficient FLO balance!");
//get unconfirmed tx list
getUnconfirmedSpent(floID).then(unconfirmedSpent => {
getUTXOs(floID).then(utxos => {
var trx = bitjs.transaction();
var utxoAmt = 0.0;
for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + 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, utxos[i].scriptPubKey);
utxoAmt += utxos[i].amount;
};
}
if (utxoAmt < totalAmt + fee)
reject("Insufficient FLO: Some UTXOs are unconfirmed");
else {
for (let i = 0; i < count; i++)
trx.addoutput(floID, splitAmt);
var change = utxoAmt - totalAmt - fee;
if (change > DEFAULT.minChangeAmt)
trx.addoutput(floID, change);
trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
/**Write data into blockchain from (and/or) to multiple floID /**Write data into blockchain from (and/or) to multiple floID
* @param {Array} senderPrivKeys List of sender private-keys * @param {Array} senderPrivKeys List of sender private-keys
* @param {string} data FLO data of the txn * @param {string} data FLO data of the txn
@ -235,11 +333,11 @@
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution * @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
* @return {Promise} * @return {Promise}
*/ */
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) { floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!Array.isArray(senderPrivKeys)) if (!Array.isArray(senderPrivKeys))
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array"); return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
if (!preserveRatio) { if (options.preserveRatio === false) {
let tmp = {}; let tmp = {};
let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length; let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length;
senderPrivKeys.forEach(key => tmp[key] = amount); senderPrivKeys.forEach(key => tmp[key] = amount);
@ -249,7 +347,7 @@
return reject("Invalid receivers: Receivers must be Array"); return reject("Invalid receivers: Receivers must be Array");
else { else {
let tmp = {}; let tmp = {};
let amount = DEFAULT.sendAmt; let amount = options.sendAmt || DEFAULT.sendAmt;
receivers.forEach(floID => tmp[floID] = amount); receivers.forEach(floID => tmp[floID] = amount);
receivers = tmp receivers = tmp
} }
@ -379,9 +477,8 @@
//Get the UTXOs of the senders //Get the UTXOs of the senders
let promises = []; let promises = [];
for (let floID in senders) for (let floID in senders)
promises.push(promisedAPI(`api/addr/${floID}/utxo`)); promises.push(getUTXOs(floID));
Promise.all(promises).then(results => { Promise.all(promises).then(results => {
let wifSeq = [];
var trx = bitjs.transaction(); var trx = bitjs.transaction();
for (let floID in senders) { for (let floID in senders) {
let utxos = results.shift(); let utxos = results.shift();
@ -391,13 +488,11 @@
sendAmt = totalSendAmt * ratio; sendAmt = totalSendAmt * ratio;
} else } else
sendAmt = senders[floID].coins + dividedFee; sendAmt = senders[floID].coins + dividedFee;
let wif = senders[floID].wif;
let utxoAmt = 0.0; let utxoAmt = 0.0;
for (let i = utxos.length - 1; for (let i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt); i--) { (i >= 0) && (utxoAmt < sendAmt); i--) {
if (utxos[i].confirmations) { if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
wifSeq.push(wif);
utxoAmt += utxos[i].amount; utxoAmt += utxos[i].amount;
} }
} }
@ -410,8 +505,8 @@
for (let floID in receivers) for (let floID in receivers)
trx.addoutput(floID, receivers[floID]); trx.addoutput(floID, receivers[floID]);
trx.addflodata(floData.replace(/\n/g, ' ')); trx.addflodata(floData.replace(/\n/g, ' '));
for (let i = 0; i < wifSeq.length; i++) for (let floID in senders)
trx.signinput(i, wifSeq[i], 1); trx.sign(senders[floID].wif, 1);
var signedTxHash = trx.serialize(); var signedTxHash = trx.serialize();
broadcastTx(signedTxHash) broadcastTx(signedTxHash)
.then(txid => resolve(txid)) .then(txid => resolve(txid))
@ -421,6 +516,259 @@
}) })
} }
//Create a multisig transaction
const 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!");
getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
getUTXOs(senderAddr).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))
});
}
//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) => {
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))
})
}
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) => {
promisedAPI(`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 //Broadcast signed Tx in blockchain using API
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -442,7 +790,7 @@
}) })
} }
floBlockchainAPI.getTx = function (txid) { const getTx = floBlockchainAPI.getTx = function (txid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/tx/${txid}`) promisedAPI(`api/tx/${txid}`)
.then(response => resolve(response)) .then(response => resolve(response))
@ -450,30 +798,89 @@
}) })
} }
//Read Txs of Address between from and to /**Wait for the given txid to get confirmation in blockchain
const readTxs = floBlockchainAPI.readTxs = function (addr, from, to) { * @param {string} txid of the transaction to wait for
* @param {int} max_retry: maximum number of retries before exiting wait. negative number = Infinite retries (DEFAULT: -1 ie, infinite retries)
* @param {Array} retry_timeout: time (seconds) between retries (DEFAULT: 20 seconds)
* @return {Promise} resolves when tx gets confirmation
*/
const waitForConfirmation = floBlockchainAPI.waitForConfirmation = function (txid, max_retry = -1, retry_timeout = 20) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`) setTimeout(function () {
getTx(txid).then(tx => {
if (!tx)
return reject("Transaction not found");
if (tx.confirmations)
return resolve(tx);
else if (max_retry === 0) //no more retries
return reject("Waiting timeout: tx still not confirmed");
else {
max_retry = max_retry < 0 ? -1 : max_retry - 1; //decrease retry count (unless infinite retries)
waitForConfirmation(txid, max_retry, retry_timeout)
.then(result => resolve(result))
.catch(error => reject(error))
}
}).catch(error => reject(error))
}, retry_timeout * 1000)
})
}
//Read Txs of Address between from and to
const readTxs = floBlockchainAPI.readTxs = function (addr, options = {}) {
return new Promise((resolve, reject) => {
let api = `api/addrs/${addr}/txs`;
//API options
let query_params = {};
if (!isUndefined(options.after) || !isUndefined(options.before)) {
if (!isUndefined(options.after))
query_params.after = options.after;
if (!isUndefined(options.before))
query_params.before = options.before;
} else {
if (!isUndefined(options.from))
query_params.from = options.from;
if (!isUndefined(options.to))
query_params.to = options.to;
}
if (!isUndefined(options.latest))
query_params.latest = options.latest;
if (!isUndefined(options.mempool))
query_params.mempool = options.mempool;
promisedAPI(api, query_params)
.then(response => resolve(response)) .then(response => resolve(response))
.catch(error => reject(error)) .catch(error => reject(error))
}); });
} }
//Read All Txs of Address (newest first) //Read All Txs of Address (newest first)
floBlockchainAPI.readAllTxs = function (addr) { const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { readTxs(addr, options).then(response => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`) if (response.incomplete) {
.then(response => resolve(response.items)) let next_options = Object.assign({}, options);
.catch(error => reject(error)); if (options.latest)
next_options.before = response.initItem; //update before for chain query (latest 1st)
else
next_options.after = response.lastItem; //update after for chain query (oldest 1st)
readAllTxs(addr, next_options).then(r => {
r.items = r.items.concat(response.items); //latest tx are 1st in array
resolve(r);
}).catch(error => reject(error)) }).catch(error => reject(error))
} else
resolve({
lastItem: response.lastItem || options.after,
items: response.items
});
})
}); });
} }
/*Read flo Data from txs of given Address /*Read flo Data from txs of given Address
options can be used to filter data options can be used to filter data
limit : maximum number of filtered data (default = 1000, negative = no limit) after : query after the given txid
ignoreOld : ignore old txs (default = 0) before : query before the given txid
mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx)
ignoreOld : ignore old txs (deprecated: support for backward compatibility only, cannot be used with 'after')
sentOnly : filters only sent data sentOnly : filters only sent data
receivedOnly: filters only received data receivedOnly: filters only received data
pattern : filters data that with JSON pattern pattern : filters data that with JSON pattern
@ -483,98 +890,149 @@
receiver : flo-id(s) of receiver receiver : flo-id(s) of receiver
*/ */
floBlockchainAPI.readData = function (addr, options = {}) { floBlockchainAPI.readData = function (addr, options = {}) {
options.limit = options.limit || 0; return new Promise((resolve, reject) => {
options.ignoreOld = options.ignoreOld || 0;
//fetch options
let query_options = {};
query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
if (!isUndefined(options.after) || !isUndefined(options.before)) {
if (!isUndefined(options.ignoreOld)) //Backward support
return reject("Invalid options: cannot use after/before and ignoreOld in same query");
//use passed after and/or before options (options remain undefined if not passed)
query_options.after = options.after;
query_options.before = options.before;
}
readAllTxs(addr, query_options).then(response => {
if (Number.isInteger(options.ignoreOld)) //backward support, cannot be used with options.after or options.before
response.items.splice(-options.ignoreOld); //negative to count from end of the array
if (typeof options.senders === "string") options.senders = [options.senders]; if (typeof options.senders === "string") options.senders = [options.senders];
if (typeof options.receivers === "string") options.receivers = [options.receivers]; if (typeof options.receivers === "string") options.receivers = [options.receivers];
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { //filter the txs based on options
var newItems = response.totalItems - options.ignoreOld; const filteredData = response.items.filter(tx => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems * 2}`).then(response => {
if (options.limit <= 0) if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query
options.limit = response.items.length; return false;
var filteredData = [];
let numToRead = response.totalItems - options.ignoreOld, if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr))
unconfirmedCount = 0; return false;
for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) { else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr)))
if (!response.items[i].confirmations) { //unconfirmed transactions return false;
unconfirmedCount++;
if (numToRead < response.items[i].length) if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr))
numToRead++; return false;
continue; else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0])))
} return false;
if (options.pattern) { if (options.pattern) {
try { try {
let jsonContent = JSON.parse(response.items[i].floData); let jsonContent = JSON.parse(tx.floData);
if (!Object.keys(jsonContent).includes(options.pattern)) if (!Object.keys(jsonContent).includes(options.pattern))
continue; return false;
} catch (error) { } catch {
continue; return false;
} }
} }
if (options.sentOnly) {
let flag = false;
for (let vin of response.items[i].vin)
if (vin.addr === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.senders)) {
let flag = false;
for (let vin of response.items[i].vin)
if (options.senders.includes(vin.addr)) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.receivedOnly) {
let flag = false;
for (let vout of response.items[i].vout)
if (vout.scriptPubKey.addresses[0] === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.receivers)) {
let flag = false;
for (let vout of response.items[i].vout)
if (options.receivers.includes(vout.scriptPubKey.addresses[0])) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.filter && !options.filter(response.items[i].floData))
continue;
if (options.filter && !options.filter(tx.floData))
return false;
return true;
}).map(tx => options.tx ? {
txid: tx.txid,
time: tx.time,
blockheight: tx.blockheight,
senders: new Set(tx.vin.map(v => v.addr)),
receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])),
data: tx.floData
} : tx.floData);
const result = { lastItem: response.lastItem };
if (options.tx)
result.items = filteredData;
else
result.data = filteredData
resolve(result);
}).catch(error => reject(error))
})
}
/*Get the latest flo Data that match the caseFn from txs of given Address
caseFn: (function) flodata => return bool value
options can be used to filter data
after : query after the given txid
before : query before the given txid
mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx)
sentOnly : filters only sent data
receivedOnly: filters only received data
tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details)
sender : flo-id(s) of sender
receiver : flo-id(s) of receiver
*/
const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) {
return new Promise((resolve, reject) => {
//fetch options
let query_options = { latest: true };
query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
if (!isUndefined(options.after)) query_options.after = options.after;
if (!isUndefined(options.before)) query_options.before = options.before;
readTxs(addr, query_options).then(response => {
if (typeof options.senders === "string") options.senders = [options.senders];
if (typeof options.receivers === "string") options.receivers = [options.receivers];
var item = response.items.find(tx => {
if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query
return false;
if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr))
return false;
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr)))
return false;
if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr))
return false;
else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0])))
return false;
return caseFn(tx.floData) ? true : false; //return only bool for find fn
});
//if item found, then resolve the result
if (!isUndefined(item)) {
const result = { lastItem: response.lastItem };
if (options.tx) { if (options.tx) {
let d = {} result.item = {
d.txid = response.items[i].txid; txid: tx.txid,
d.time = response.items[i].time; time: tx.time,
d.blockheight = response.items[i].blockheight; blockheight: tx.blockheight,
d.senders = new Set(response.items[i].vin.map(v => v.addr)); senders: new Set(tx.vin.map(v => v.addr)),
d.receivers = new Set(response.items[i].vout.map(v => v.scriptPubKey.addresses[0])); receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])),
d.data = response.items[i].floData; data: tx.floData
filteredData.push(d); }
} else } else
filteredData.push(response.items[i].floData); result.data = tx.floData;
return resolve(result);
} }
resolve({ //else if address needs chain query
totalTxs: response.totalItems - unconfirmedCount, else if (response.incomplete) {
data: filteredData let next_options = Object.assign({}, options);
}); options.before = response.initItem; //this fn uses latest option, so using before to chain query
}).catch(error => { getLatestData(addr, caseFn, next_options).then(r => {
reject(error); r.lastItem = response.lastItem; //update last key as it should be the newest tx
}); resolve(r);
}).catch(error => { }).catch(error => reject(error))
reject(error);
});
});
} }
//no data match the caseFn, resolve just the lastItem
else
resolve({ lastItem: response.lastItem });
}).catch(error => reject(error))
})
}
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {}); })('object' === typeof module ? module.exports : window.floBlockchainAPI = {});

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floCrypto v2.3.3e (function (EXPORTS) { //floCrypto v2.3.6a
/* FLO Crypto Operators */ /* FLO Crypto Operators */
'use strict'; 'use strict';
const floCrypto = EXPORTS; const floCrypto = EXPORTS;
@ -152,6 +152,19 @@
newID: { newID: {
get: () => generateNewID() get: () => generateNewID()
}, },
hashID: {
value: (str) => {
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
}
},
tmpID: { tmpID: {
get: () => { get: () => {
let bytes = Crypto.util.randomBytes(20); let bytes = Crypto.util.randomBytes(20);
@ -222,7 +235,7 @@
key.setCompressed(true); key.setCompressed(true);
if (isfloID && pubKey_floID == key.getBitcoinAddress()) if (isfloID && pubKey_floID == key.getBitcoinAddress())
return true; return true;
else if (!isfloID && pubKey_floID == key.getPubKeyHex()) else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
return true; return true;
else else
return false; return false;
@ -231,12 +244,36 @@
} }
} }
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
return null;
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
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 //Check if the given flo-id is valid or not
floCrypto.validateFloID = function (floID) { floCrypto.validateFloID = function (floID, regularOnly = false) {
if (!floID) if (!floID)
return false; return false;
try { try {
let addr = new Bitcoin.Address(floID); let addr = new Bitcoin.Address(floID);
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
return false;
return true; return true;
} catch { } catch {
return false; return false;
@ -266,13 +303,15 @@
return false; 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) { floCrypto.verifyPubKey = function (pubKeyHex, address) {
let raw = decodeAddress(address), let raw = decodeAddress(address);
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { if (!raw)
asBytes: true return;
}))); let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
return raw ? pub_hash === raw.hex : false; 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 //Convert the given address (any blockchain) to equivalent floID
@ -282,7 +321,7 @@
let raw = decodeAddress(address); let raw = decodeAddress(address);
if (!raw) if (!raw)
return; 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))) if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
return; return;
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version))) if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
@ -297,6 +336,50 @@
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4))); return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
} }
//Convert raw address bytes to floID
floCrypto.rawToFloID = function (raw_bytes) {
if (typeof raw_bytes === 'string')
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
if (raw_bytes.length != 20)
return null;
raw_bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
asBytes: true
}), {
asBytes: true
});
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) //Checks if the given addresses (any blockchain) are same (w.r.t keys)
floCrypto.isSameAddr = function (addr1, addr2) { floCrypto.isSameAddr = function (addr1, addr2) {
if (!addr1 || !addr2) if (!addr1 || !addr2)
@ -305,9 +388,14 @@
raw2 = decodeAddress(addr2); raw2 = decodeAddress(addr2);
if (!raw1 || !raw2) if (!raw1 || !raw2)
return false; 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; return raw1.hex === raw2.hex;
} }
}
const decodeAddress = floCrypto.decodeAddr = function (address) { const decodeAddress = floCrypto.decodeAddr = function (address) {
if (!address) if (!address)
@ -326,7 +414,7 @@
hex: Crypto.util.bytesToHex(bytes), hex: Crypto.util.bytesToHex(bytes),
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); let decode = coinjs.bech32_decode(address);
if (decode) { if (decode) {
let bytes = decode.data; let bytes = decode.data;

View File

@ -1737,13 +1737,15 @@
trusted = new Set(); trusted = new Set();
assets = new Set(); assets = new Set();
tags = new Set(); tags = new Set();
lastTx = 0; lastTx = undefined;
} }
floBlockchainAPI.readData(DEFAULT.marketID, {
ignoreOld: lastTx, var query_options = { sentOnly: true, pattern: DEFAULT.marketApp };
sentOnly: true, if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
pattern: DEFAULT.marketApp query_options.after = lastTx;
}).then(result => { else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
query_options.ignoreOld = parseInt(lastTx);
floBlockchainAPI.readData(DEFAULT.marketID, query_options).then(result => {
result.data.reverse().forEach(data => { result.data.reverse().forEach(data => {
var content = JSON.parse(data)[DEFAULT.marketApp]; var content = JSON.parse(data)[DEFAULT.marketApp];
//Node List //Node List

View File

@ -1,11 +1,11 @@
(function(EXPORTS) { //floTokenAPI v1.0.3c (function (EXPORTS) { //floTokenAPI v1.0.4a
/* Token Operator to send/receive tokens via blockchain using API calls*/ /* Token Operator to send/receive tokens via blockchain using API calls*/
'use strict'; 'use strict';
const tokenAPI = EXPORTS; const tokenAPI = EXPORTS;
const DEFAULT = { const DEFAULT = {
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/", apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
currency: "rupee" currency: floGlobals.currency || "rupee"
} }
Object.defineProperties(tokenAPI, { Object.defineProperties(tokenAPI, {
@ -77,6 +77,70 @@
}); });
} }
function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) {
return new Promise((resolve, reject) => {
var trx = bitjs.transaction();
trx.addinput(utxo, vout, scriptPubKey)
trx.addoutput(receiverID, floBlockchainAPI.sendAmt);
trx.addflodata(`send ${amount} ${token}#`);
var signedTxHash = trx.sign(privKey, 1);
floBlockchainAPI.broadcastTx(signedTxHash)
.then(txid => resolve([receiverID, txid]))
.catch(error => reject([receiverID, error]))
})
}
//bulk transfer tokens
tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) {
return new Promise((resolve, reject) => {
if (typeof receivers !== 'object')
return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}")
let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers);
let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id));
let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0);
if (invalidReceivers.length)
return reject(`Invalid receivers: ${invalidReceivers}`);
else if (invalidAmount.length)
return reject(`Invalid amounts: ${invalidAmount}`);
if (receiver_list.length == 0)
return reject("Receivers cannot be empty");
if (receiver_list.length == 1) {
let receiver = receiver_list[0], amount = amount_list[0];
floTokenAPI.sendToken(privKey, amount, receiver, "", token)
.then(txid => resolve({ success: { [receiver]: txid } }))
.catch(error => reject(error))
} else {
//check for token balance
floTokenAPI.getBalance(sender, token).then(token_balance => {
let total_token_amout = amount_list.reduce((a, e) => a + e, 0);
if (total_token_amout > token_balance)
return reject(`Insufficient ${token}# balance`);
//split utxos
floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => {
//wait for the split utxo to get confirmation
floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => {
//send tokens using the split-utxo
var scriptPubKey = split_tx.vout[0].scriptPubKey.hex;
let promises = [];
for (let i in receiver_list)
promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey));
Promise.allSettled(promises).then(results => {
let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value));
let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason));
resolve({ success, failed });
})
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}
})
}
tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) { tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`) fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)

View File

@ -1,4 +1,4 @@
(function(GLOBAL) { //lib v1.3.1 (function (GLOBAL) { //lib v1.4.2b
'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.
@ -4349,20 +4349,18 @@
var bitjs = GLOBAL.bitjs = function () { }; 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 */ /* public vars */
bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23 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.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; 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 */ /* provide a privkey and return an WIF */
bitjs.privkey2wif = function (h) { bitjs.privkey2wif = function (h) {
var r = Crypto.util.hexToBytes(h); var r = Crypto.util.hexToBytes(h);
@ -4461,7 +4459,46 @@
return B58.encode(r.concat(checksum)); return B58.encode(r.concat(checksum));
} }
bitjs.transaction = function() { /* 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 (tx_data = undefined) {
var btrx = {}; var btrx = {};
btrx.version = 2; //flochange look at this version btrx.version = 2; //flochange look at this version
btrx.inputs = []; btrx.inputs = [];
@ -4476,7 +4513,6 @@
'hash': txid, 'hash': txid,
'index': index '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.script = Crypto.util.hexToBytes(scriptPubKey); //push previous output pubkey script
o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0); o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0);
return this.inputs.push(o); return this.inputs.push(o);
@ -4485,26 +4521,41 @@
btrx.addoutput = function (address, value) { btrx.addoutput = function (address, value) {
var o = {}; var o = {};
var buf = []; var buf = [];
var addrDecoded = btrx.addressDecode(address); var addr = this.addressDecode(address);
o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10); o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10);
if (addr.version === bitjs.pub) { // regular address
buf.push(118); //OP_DUP buf.push(118); //OP_DUP
buf.push(169); //OP_HASH160 buf.push(169); //OP_HASH160
buf.push(addrDecoded.length); buf = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes
buf = buf.concat(addrDecoded); // address in bytes
buf.push(136); //OP_EQUALVERIFY buf.push(136); //OP_EQUALVERIFY
buf.push(172); //OP_CHECKSIG buf.push(172); //OP_CHECKSIG
} else if (addr.version === bitjs.multisig) { // multisig address
buf.push(169); //OP_HASH160
buf = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes
buf.push(135); //OP_EQUAL
}
o.script = buf; o.script = buf;
return this.outputs.push(o); 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 = data;
this.floData = txcomments; return this.floData;
return this.floData; //flochange .. returning the txcomments -- check if the function return will assign
} }
// Only standard addresses // Only standard addresses (standard multisig supported)
btrx.addressDecode = function (address) { btrx.addressDecode = function (address) {
var bytes = B58.decode(address); var bytes = B58.decode(address);
var front = bytes.slice(0, bytes.length - 4); var front = bytes.slice(0, bytes.length - 4);
@ -4515,7 +4566,10 @@
asBytes: true asBytes: true
}).slice(0, 4); }).slice(0, 4);
if (checksum + "" == back + "") { if (checksum + "" == back + "") {
return front.slice(1); return {
version: front[0],
bytes: front.slice(1)
};
} }
} }
@ -4740,6 +4794,83 @@
return KBigInt; 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 = [];
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);
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 = [];
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 */ /* sign a "standard" input */
btrx.signinput = function (index, wif, sigHashType) { btrx.signinput = function (index, wif, sigHashType) {
var key = bitjs.wif2pubkey(wif); var key = bitjs.wif2pubkey(wif);
@ -4747,8 +4878,7 @@
var signature = this.transactionSig(index, wif, shType); var signature = this.transactionSig(index, wif, shType);
var buf = []; var buf = [];
var sigBytes = Crypto.util.hexToBytes(signature); var sigBytes = Crypto.util.hexToBytes(signature);
buf.push(sigBytes.length); buf = this.writeBytesToScriptBuffer(buf, sigBytes);
buf = buf.concat(sigBytes);
var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']); var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']);
buf.push(pubKeyBytes.length); buf.push(pubKeyBytes.length);
buf = buf.concat(pubKeyBytes); buf = buf.concat(pubKeyBytes);
@ -4756,15 +4886,98 @@
return true; 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 = this.writeBytesToScriptBuffer(buf, sigsList[y]);
break; //ensures duplicate sigs from same pubkey are not added
}
}
}
//append redeemscript
buf = this.writeBytesToScriptBuffer(buf, redeemScript);
this.inputs[index].script = buf;
return true;
}
/* sign inputs */ /* sign inputs */
btrx.sign = function (wif, sigHashType) { btrx.sign = function (wif, sigHashType) {
var shType = sigHashType || 1; var shType = sigHashType || 1;
for (var i = 0; i < this.inputs.length; i++) { for (var i = 0; i < this.inputs.length; i++) {
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); this.signinput(i, wif, shType);
} else if (decodedScript.type == "multisig") { //multisig
this.signmultisig(i, wif, shType);
}
} }
return this.serialize(); 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 */ /* serialize a transaction */
btrx.serialize = function () { btrx.serialize = function () {
@ -4793,28 +5006,85 @@
} }
buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4)); buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4));
var flohex = ascii_to_hexa(this.floData);
var floDataCount = this.floData.length; //flochange -- append floData field
var floDataCountString; buffer = buffer.concat(bitjs.numToVarInt(this.floData.length));
//flochange -- creating unique data character count logic for floData. This string is prefixed before actual floData string in Raw Transaction buffer = buffer.concat(bitjs.strToBytes(this.floData))
if (floDataCount < 16) {
floDataCountString = floDataCount.toString(16); return Crypto.util.bytesToHex(buffer);
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";
} }
/* deserialize a transaction */
return Crypto.util.bytesToHex(buffer) + floDataCountString + flohex; // flochange -- Addition of floDataCountString and floData in serialization 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; return btrx;
@ -4856,6 +5126,36 @@
else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1)); 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 */ /* clone an object */
bitjs.clone = function (obj) { bitjs.clone = function (obj) {
if (obj == null || typeof (obj) != 'object') return obj; if (obj == null || typeof (obj) != 'object') return obj;
@ -5020,17 +5320,25 @@
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js //https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js
Bitcoin.Address = function (bytes) { 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) { if ("string" == typeof bytes) {
bytes = Bitcoin.Address.decodeString(bytes, this.version); var d = Bitcoin.Address.decodeString(bytes);
bytes = d.hash;
if (d.version == Bitcoin.Address.standardVersion || d.version == Bitcoin.Address.multisigVersion)
this.version = d.version;
else throw "Version (prefix) " + d.version + " not supported!";
} else {
this.version = Bitcoin.Address.standardVersion;
} }
this.hash = bytes; 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)
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. * Serialize this object as a standard Bitcoin address.
@ -5059,7 +5367,7 @@
/** /**
* Parse a Bitcoin address contained in a string. * 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 bytes = Bitcoin.Base58.decode(string);
var hash = bytes.slice(0, 21); var hash = bytes.slice(0, 21);
var checksum = Crypto.SHA256(Crypto.SHA256(hash, { var checksum = Crypto.SHA256(Crypto.SHA256(hash, {
@ -5075,11 +5383,12 @@
throw "Checksum validation failed!"; throw "Checksum validation failed!";
} }
if (version != hash.shift()) { /*if (version != hash.shift()) {
throw "Version " + hash.shift() + " not supported!"; 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 //https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js
Bitcoin.ECDSA = (function () { Bitcoin.ECDSA = (function () {
@ -6478,6 +6787,21 @@
}; };
} }
//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,
'scripthash': Crypto.util.bytesToHex(program),
'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 +6891,19 @@
}; };
} }
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': redeemscript,
'scripthash': 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 +6995,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 +7038,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 +7056,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 +7673,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 +7863,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 +7898,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 = 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 +8037,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 +8066,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 +8230,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 +8428,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 +8642,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 +8698,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 +9097,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;
} }

View File

@ -316,7 +316,7 @@ function deleteTableData(data) {
data.forEach(r => r.t_name in delete_needed ? delete_needed[r.t_name].push(r.id) : delete_needed[r.t_name] = [r.id]); data.forEach(r => r.t_name in delete_needed ? delete_needed[r.t_name].push(r.id) : delete_needed[r.t_name] = [r.id]);
let queries = []; let queries = [];
for (let table in delete_needed) for (let table in delete_needed)
queries.push("DELETE FROM ?? WHERE id IN (?)", [table, delete_needed[table]]); queries.push(["DELETE FROM ?? WHERE id IN (?)", [table, delete_needed[table]]]);
DB.transaction(queries).then(_ => resolve(true)).catch(error => reject(error)); DB.transaction(queries).then(_ => resolve(true)).catch(error => reject(error));
}) })
} }

View File

@ -139,7 +139,7 @@ function getTableHashes(table) {
//columns //columns
let columns = result.map(r => r["Field"]).sort(); let columns = result.map(r => r["Field"]).sort();
//select statement //select statement
let statement = "SELECT CEIL(id/?) as group_id"; let statement = "SELECT CEIL(id/?) as group_id,";
let query_values = [HASH_N_ROW]; let query_values = [HASH_N_ROW];
//aggregate column values //aggregate column values
let col_aggregate = columns.map(c => "IFNULL(CRC32(??), 0)").join('+'); let col_aggregate = columns.map(c => "IFNULL(CRC32(??), 0)").join('+');

View File

@ -40,13 +40,16 @@ function refreshData(startup = false) {
function refreshDataFromBlockchain() { function refreshDataFromBlockchain() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("SELECT num FROM LastTx WHERE floID=?", [floGlobals.adminID]).then(result => { DB.query("SELECT txid FROM LastTx WHERE floID=?", [floGlobals.adminID]).then(result => {
let lastTx = result.length ? result[0].num : 0; var query_options = { sentOnly: true, pattern: floGlobals.application };
floBlockchainAPI.readData(floGlobals.adminID, {
ignoreOld: lastTx, let lastTx = result.length ? result[0].txid : undefined;
sentOnly: true, if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
pattern: floGlobals.application query_options.after = lastTx;
}).then(result => { else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
query_options.ignoreOld = parseInt(lastTx);
floBlockchainAPI.readData(floGlobals.adminID, query_options).then(result => {
let promises = [], let promises = [],
nodes_change = false, nodes_change = false,
assets_change = false, assets_change = false,
@ -96,7 +99,7 @@ function refreshDataFromBlockchain() {
promises.push(`UPDATE TagList WHERE tag=? SET ${a}=?`, [t, content.Tag.update[t][a]]); promises.push(`UPDATE TagList WHERE tag=? SET ${a}=?`, [t, content.Tag.update[t][a]]);
} }
}); });
promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", [[floGlobals.adminID, result.totalTxs], result.totalTxs])); promises.push(DB.query("INSERT INTO LastTx (floID, txid) VALUE (?) ON DUPLICATE KEY UPDATE txid=?", [[floGlobals.adminID, result.lastItem], result.lastItem]));
//Check if all save process were successful //Check if all save process were successful
Promise.allSettled(promises).then(results => { Promise.allSettled(promises).then(results => {
//console.debug(results.filter(r => r.status === "rejected")); //console.debug(results.filter(r => r.status === "rejected"));

View File

@ -203,16 +203,21 @@ bobsFund.config = {
function refreshBlockchainData(nodeList = []) { function refreshBlockchainData(nodeList = []) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("SELECT num FROM LastTx WHERE floID=?", [bobsFund.config.adminID]).then(result => { DB.query("SELECT txid FROM LastTx WHERE floID=?", [bobsFund.config.adminID]).then(result => {
let lastTx = result.length ? result[0].num : 0;
floBlockchainAPI.readData(bobsFund.config.adminID, { var query_options = {
ignoreOld: lastTx, senders: nodeList.concat(bobsFund.config.adminID),
senders: nodeList.concat(bobsFund.config.adminID), //sentOnly: true, tx: true, filter: d => d.startsWith(bobsFund.productStr)
tx: true, };
filter: d => d.startsWith(bobsFund.productStr) let lastTx = result.length ? result[0].txid : undefined;
}).then(result => { if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
query_options.after = lastTx;
else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
query_options.ignoreOld = parseInt(lastTx);
floBlockchainAPI.readData(bobsFund.config.adminID, query_options).then(result => {
let txQueries = []; let txQueries = [];
result.data.reverse().forEach(d => { result.items.reverse().forEach(d => {
let fund = bobsFund.parse(d.data); let fund = bobsFund.parse(d.data);
if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) { if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) {
let fund_id = d.data.match(/continue: [a-z0-9]{64}\|/); let fund_id = d.data.match(/continue: [a-z0-9]{64}\|/);
@ -240,10 +245,10 @@ function refreshBlockchainData(nodeList = []) {
} }
} }
}); });
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", txQueries.push(["INSERT INTO LastTx (floID, txid) VALUE (?) ON DUPLICATE KEY UPDATE txid=?",
[[bobsFund.config.adminID, result.totalTxs], result.totalTxs]]) [[bobsFund.config.adminID, result.lastItem], result.lastItem]])
DB.transaction(txQueries) DB.transaction(txQueries)
.then(_ => resolve(result.totalTxs)) .then(_ => resolve(result.lastItem))
.catch(error => reject(["Bobs-Fund refresh data failed!", error])); .catch(error => reject(["Bobs-Fund refresh data failed!", error]));
}).catch(error => reject(error)) }).catch(error => reject(error))
}).catch(error => reject(error)) }).catch(error => reject(error))

View File

@ -186,16 +186,21 @@ blockchainBond.config = {
function refreshBlockchainData(nodeList = []) { function refreshBlockchainData(nodeList = []) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("SELECT num FROM LastTx WHERE floID=?", [blockchainBond.config.adminID]).then(result => { DB.query("SELECT txid FROM LastTx WHERE floID=?", [blockchainBond.config.adminID]).then(result => {
let lastTx = result.length ? result[0].num : 0;
floBlockchainAPI.readData(blockchainBond.config.adminID, { var query_options = {
ignoreOld: lastTx, senders: nodeList.concat(blockchainBond.config.adminID),
senders: nodeList.concat(blockchainBond.config.adminID), //sentOnly: true, tx: true, filter: d => d.startsWith(blockchainBond.productStr)
tx: true, };
filter: d => d.startsWith(blockchainBond.productStr) let lastTx = result.length ? result[0].txid : undefined;
}).then(result => { if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
query_options.after = lastTx;
else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
query_options.ignoreOld = parseInt(lastTx);
floBlockchainAPI.readData(blockchainBond.config.adminID, query_options).then(result => {
let txQueries = []; let txQueries = [];
result.data.reverse().forEach(d => { result.items.reverse().forEach(d => {
let bond = d.senders.has(blockchainBond.config.adminID) ? blockchainBond.parse.main(d.data) : null; let bond = d.senders.has(blockchainBond.config.adminID) ? blockchainBond.parse.main(d.data) : null;
if (bond && bond.amount) if (bond && bond.amount)
txQueries.push(["INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUE (?) ON DUPLICATE KEY UPDATE bond_id=bond_id", txQueries.push(["INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUE (?) ON DUPLICATE KEY UPDATE bond_id=bond_id",
@ -207,10 +212,10 @@ function refreshBlockchainData(nodeList = []) {
[d.txid, details.amountFinal, details.bondID]]); [d.txid, details.amountFinal, details.bondID]]);
} }
}); });
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", txQueries.push(["INSERT INTO LastTx (floID, txid) VALUE (?) ON DUPLICATE KEY UPDATE txid=?",
[[blockchainBond.config.adminID, result.totalTxs], result.totalTxs]]) [[blockchainBond.config.adminID, result.lastItem], result.lastItem]])
DB.transaction(txQueries) DB.transaction(txQueries)
.then(_ => resolve(result.totalTxs)) .then(_ => resolve(result.lastItem))
.catch(error => reject(["Blockchain-bonds refresh data failed!", error])); .catch(error => reject(["Blockchain-bonds refresh data failed!", error]));
}).catch(error => reject(error)) }).catch(error => reject(error))
}).catch(error => reject(error)) }).catch(error => reject(error))