From e8bc41974cf98644d50d86ef45c8ed4625f9bfbf Mon Sep 17 00:00:00 2001 From: sairajzero Date: Wed, 10 May 2023 02:35:55 +0530 Subject: [PATCH] moving required stdop files to scripts/ --- btcOperator.js | 888 -------------- floCloudAPI.js | 1055 ----------------- floDapps.js | 845 ------------- compactIDB.js => scripts/compactIDB.js | 0 .../floBlockchainAPI.js | 0 floCrypto.js => scripts/floCrypto.js | 0 floTokenAPI.js => scripts/floTokenAPI.js | 0 lib.js => scripts/lib.js | 0 8 files changed, 2788 deletions(-) delete mode 100644 btcOperator.js delete mode 100644 floCloudAPI.js delete mode 100644 floDapps.js rename compactIDB.js => scripts/compactIDB.js (100%) rename floBlockchainAPI.js => scripts/floBlockchainAPI.js (100%) rename floCrypto.js => scripts/floCrypto.js (100%) rename floTokenAPI.js => scripts/floTokenAPI.js (100%) rename lib.js => scripts/lib.js (100%) diff --git a/btcOperator.js b/btcOperator.js deleted file mode 100644 index ff86fb9..0000000 --- a/btcOperator.js +++ /dev/null @@ -1,888 +0,0 @@ -(function (EXPORTS) { //btcOperator v1.1.2a - /* BTC Crypto and API Operator */ - const btcOperator = EXPORTS; - - //This library uses API provided by chain.so (https://chain.so/) - const URL = "https://blockchain.info/"; - - const fetch_api = btcOperator.fetch = function (api, json_res = true) { - return new Promise((resolve, reject) => { - console.debug(URL + api); - fetch(URL + api).then(response => { - if (response.ok) { - (json_res ? response.json() : response.text()) - .then(result => resolve(result)) - .catch(error => reject(error)) - } else { - response.json() - .then(result => reject(result)) - .catch(error => reject(error)) - } - }).catch(error => reject(error)) - }) - }; - - const SATOSHI_IN_BTC = 1e8; - - const util = btcOperator.util = {}; - - util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8)); - util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC); - - function get_fee_rate() { - return new Promise((resolve, reject) => { - fetch('https://api.blockchain.info/mempool/fees').then(response => { - if (response.ok) - response.json() - .then(result => resolve(util.Sat_to_BTC(result.regular))) - .catch(error => reject(error)); - else - reject(response); - }).catch(error => reject(error)) - }) - } - - const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => { - let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction'; - fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: "rawtx=" + rawTxHex - }).then(response => { - response.text().then(resultText => { - let r = resultText.match(/.*<\/result>/); - if (!r) - reject(resultText); - else { - r = r.pop().replace('', '').replace('', ''); - if (r == '1') { - let txid = resultText.match(/.*<\/txid>/).pop().replace('', '').replace('', ''); - resolve(txid); - } else if (r == '0') { - let error = resultText.match(/.*<\/response>/).pop().replace('', '').replace('', ''); - reject(decodeURIComponent(error.replace(/\+/g, " "))); - } else reject(resultText); - } - }).catch(error => reject(error)) - }).catch(error => reject(error)) - }); - - Object.defineProperties(btcOperator, { - newKeys: { - get: () => { - let r = coinjs.newKeys(); - r.segwitAddress = coinjs.segwitAddress(r.pubkey).address; - r.bech32Address = coinjs.bech32Address(r.pubkey).address; - return r; - } - }, - pubkey: { - value: key => key.length >= 66 ? key : (key.length == 64 ? coinjs.newPubkey(key) : coinjs.wif2pubkey(key).pubkey) - }, - address: { - value: (key, prefix = undefined) => coinjs.pubkey2address(btcOperator.pubkey(key), prefix) - }, - segwitAddress: { - value: key => coinjs.segwitAddress(btcOperator.pubkey(key)).address - }, - bech32Address: { - value: key => coinjs.bech32Address(btcOperator.pubkey(key)).address - } - }); - - coinjs.compressed = true; - - const verifyKey = btcOperator.verifyKey = function (addr, key) { - if (!addr || !key) - return undefined; - switch (coinjs.addressDecode(addr).type) { - case "standard": - return btcOperator.address(key) === addr; - case "multisig": - return btcOperator.segwitAddress(key) === addr; - case "bech32": - return btcOperator.bech32Address(key) === addr; - default: - return null; - } - } - - const validateAddress = btcOperator.validateAddress = function (addr) { - if (!addr) - return undefined; - let type = coinjs.addressDecode(addr).type; - if (["standard", "multisig", "bech32", "multisigBech32"].includes(type)) - return type; - else - return false; - } - - btcOperator.multiSigAddress = function (pubKeys, minRequired, bech32 = true) { - if (!Array.isArray(pubKeys)) - throw "pubKeys must be an array of public keys"; - else if (pubKeys.length < minRequired) - throw "minimum required should be less than the number of pubKeys"; - if (bech32) - return coinjs.pubkeys2MultisigAddressBech32(pubKeys, minRequired); - else - return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired); - } - - btcOperator.decodeRedeemScript = function (redeemScript, bech32 = true) { - let script = coinjs.script(); - let decoded = (bech32) ? - script.decodeRedeemScriptBech32(redeemScript) : - script.decodeRedeemScript(redeemScript); - if (!decoded) - return null; - return { - address: decoded.address, - pubKeys: decoded.pubkeys, - redeemScript: decoded.redeemscript, - required: decoded.signaturesRequired - } - - } - - //convert from one blockchain to another blockchain (target version) - btcOperator.convert = {}; - - btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) { - let keyHex = util.decodeLegacy(source_wif).hex; - if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex)) - return null; - else - return util.encodeLegacy(keyHex, target_version); - } - - btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) { - let rawHex = util.decodeLegacy(source_addr).hex; - if (!rawHex) - return null; - else - return util.encodeLegacy(rawHex, target_version); - } - - btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { - let rawHex = util.decodeLegacy(source_addr).hex; - if (!rawHex) - return null; - else - return util.encodeBech32(rawHex, target_version, target_hrp); - } - - btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { - let rawHex = util.decodeBech32(source_addr).hex; - if (!rawHex) - return null; - else - return util.encodeBech32(rawHex, target_version, target_hrp); - } - - btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) { - let rawHex = util.decodeBech32(source_addr).hex; - if (!rawHex) - return null; - else - return util.encodeLegacy(rawHex, target_version); - } - - btcOperator.convert.multisig2multisig = function (source_addr, target_version = coinjs.multisig) { - let rawHex = util.decodeLegacy(source_addr).hex; - if (!rawHex) - return null; - else - return util.encodeLegacy(rawHex, target_version); - } - - btcOperator.convert.bech2multisig = function (source_addr, target_version = coinjs.multisig) { - let rawHex = util.decodeBech32(source_addr).hex; - if (!rawHex) - return null; - else { - rawHex = Crypto.util.bytesToHex(ripemd160(Crypto.util.hexToBytes(rawHex), { asBytes: true })); - return util.encodeLegacy(rawHex, target_version); - } - } - - util.decodeLegacy = function (source) { - var decode = coinjs.base58decode(source); - var raw = decode.slice(0, decode.length - 4), - checksum = decode.slice(decode.length - 4); - var hash = Crypto.SHA256(Crypto.SHA256(raw, { - asBytes: true - }), { - asBytes: true - }); - if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) - return false; - let version = raw.shift(); - return { - version: version, - hex: Crypto.util.bytesToHex(raw) - } - } - - util.encodeLegacy = function (hex, version) { - var bytes = Crypto.util.hexToBytes(hex); - bytes.unshift(version); - var hash = Crypto.SHA256(Crypto.SHA256(bytes, { - asBytes: true - }), { - asBytes: true - }); - var checksum = hash.slice(0, 4); - return coinjs.base58encode(bytes.concat(checksum)); - } - - util.decodeBech32 = function (source) { - let decode = coinjs.bech32_decode(source); - if (!decode) - return false; - var raw = decode.data; - let version = raw.shift(); - raw = coinjs.bech32_convert(raw, 5, 8, false); - return { - hrp: decode.hrp, - version: version, - hex: Crypto.util.bytesToHex(raw) - } - } - - util.encodeBech32 = function (hex, version, hrp) { - var bytes = Crypto.util.hexToBytes(hex); - bytes = coinjs.bech32_convert(bytes, 8, 5, true); - bytes.unshift(version) - return coinjs.bech32_encode(hrp, bytes); - } - - //BTC blockchain APIs - - btcOperator.getBalance = addr => new Promise((resolve, reject) => { - fetch_api(`q/addressbalance/${addr}`) - .then(result => resolve(util.Sat_to_BTC(result))) - .catch(error => reject(error)) - }); - - const BASE_TX_SIZE = 12, - BASE_INPUT_SIZE = 41, - LEGACY_INPUT_SIZE = 107, - BECH32_INPUT_SIZE = 27, - BECH32_MULTISIG_INPUT_SIZE = 35, - SEGWIT_INPUT_SIZE = 59, - MULTISIG_INPUT_SIZE_ES = 351, - BASE_OUTPUT_SIZE = 9, - LEGACY_OUTPUT_SIZE = 25, - BECH32_OUTPUT_SIZE = 23, - BECH32_MULTISIG_OUTPUT_SIZE = 34, - SEGWIT_OUTPUT_SIZE = 23; - - function _redeemScript(addr, key) { - let decode = coinjs.addressDecode(addr); - switch (decode.type) { - case "standard": - return false; - case "multisig": - return key ? coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript : null; - case "bech32": - return decode.redeemscript; - default: - return null; - } - } - - function _sizePerInput(addr, rs) { - switch (coinjs.addressDecode(addr).type) { - case "standard": - return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE; - case "bech32": - return BASE_INPUT_SIZE + BECH32_INPUT_SIZE; - case "multisigBech32": - return BASE_INPUT_SIZE + BECH32_MULTISIG_INPUT_SIZE; - case "multisig": - switch (coinjs.script().decodeRedeemScript(rs).type) { - case "segwit__": - return BASE_INPUT_SIZE + SEGWIT_INPUT_SIZE; - case "multisig__": - return BASE_INPUT_SIZE + MULTISIG_INPUT_SIZE_ES; - default: - return null; - }; - default: - return null; - } - } - - function _sizePerOutput(addr) { - switch (coinjs.addressDecode(addr).type) { - case "standard": - return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE; - case "bech32": - return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE; - case "multisigBech32": - return BASE_OUTPUT_SIZE + BECH32_MULTISIG_OUTPUT_SIZE; - case "multisig": - return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE; - default: - return null; - } - } - - function validateTxParameters(parameters) { - let invalids = []; - //sender-ids - if (parameters.senders) { - if (!Array.isArray(parameters.senders)) - parameters.senders = [parameters.senders]; - parameters.senders.forEach(id => !validateAddress(id) ? invalids.push(id) : null); - if (invalids.length) - throw "Invalid senders:" + invalids; - } - if (parameters.privkeys) { - if (!Array.isArray(parameters.privkeys)) - parameters.privkeys = [parameters.privkeys]; - if (parameters.senders.length != parameters.privkeys.length) - throw "Array length for senders and privkeys should be equal"; - parameters.senders.forEach((id, i) => { - let key = parameters.privkeys[i]; - if (!verifyKey(id, key)) //verify private-key - invalids.push(id); - if (key.length === 64) //convert Hex to WIF if needed - parameters.privkeys[i] = coinjs.privkey2wif(key); - }); - if (invalids.length) - throw "Invalid private key for address:" + invalids; - } - //receiver-ids (and change-id) - if (!Array.isArray(parameters.receivers)) - parameters.receivers = [parameters.receivers]; - parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null); - if (invalids.length) - throw "Invalid receivers:" + invalids; - if (parameters.change_address && !validateAddress(parameters.change_address)) - throw "Invalid change_address:" + parameters.change_address; - //fee and amounts - if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc) - throw "Invalid fee:" + parameters.fee; - if (!Array.isArray(parameters.amounts)) - parameters.amounts = [parameters.amounts]; - if (parameters.receivers.length != parameters.amounts.length) - throw "Array length for receivers and amounts should be equal"; - parameters.amounts.forEach(a => typeof a !== "number" || a <= 0 ? invalids.push(a) : null); - if (invalids.length) - throw "Invalid amounts:" + invalids; - //return - return parameters; - } - - function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) { - return new Promise((resolve, reject) => { - let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8)); - const tx = coinjs.transaction(); - let output_size = addOutputs(tx, receivers, amounts, change_address); - addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => { - if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change) - tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi - if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver - let fee_remaining = util.BTC_to_Sat(result.fee); - for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) { - if (fee_remaining < tx.outs[i].value) { - tx.outs[i].value -= fee_remaining; - fee_remaining = 0; - } else { - fee_remaining -= tx.outs[i].value; - tx.outs[i].value = 0; - } - } - if (fee_remaining > 0) - return reject("Send amount is less than fee"); - - } - tx.outs = tx.outs.filter(o => o.value != 0); //remove all output with value 0 - result.output_size = output_size; - result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0); - result.total_size = BASE_TX_SIZE + output_size + result.input_size; - result.transaction = tx; - resolve(result); - }).catch(error => reject(error)) - }) - } - - function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) { - return new Promise((resolve, reject) => { - if (fee !== null) { - addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => { - result.fee = fee; - resolve(result); - }).catch(error => reject(error)) - } else { - get_fee_rate().then(fee_rate => { - let net_fee = BASE_TX_SIZE * fee_rate; - net_fee += (output_size * fee_rate); - (fee_from_receiver ? - addUTXOs(tx, senders, redeemScripts, total_amount, false) : - addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate) - ).then(result => { - result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8)); - result.fee_rate = fee_rate; - resolve(result); - }).catch(error => reject(error)) - }).catch(error => reject(error)) - } - }) - } - - function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) { - return new Promise((resolve, reject) => { - required_amount = parseFloat(required_amount.toFixed(8)); - if (typeof rec_args.n === "undefined") { - rec_args.n = 0; - rec_args.input_size = 0; - rec_args.input_amount = 0; - } - if (required_amount <= 0) - return resolve({ - input_size: rec_args.input_size, - input_amount: rec_args.input_amount, - change_amount: required_amount * -1 //required_amount will be -ve of change_amount - }); - else if (rec_args.n >= senders.length) - return reject("Insufficient Balance"); - let addr = senders[rec_args.n], - rs = redeemScripts[rec_args.n]; - let addr_type = coinjs.addressDecode(addr).type; - let size_per_input = _sizePerInput(addr, rs); - fetch_api(`unspent?active=${addr}`).then(result => { - let utxos = result.unspent_outputs; - console.debug("add-utxo", addr, rs, required_amount, utxos); - for (let i = 0; i < utxos.length && required_amount > 0; i++) { - if (!utxos[i].confirmations) //ignore unconfirmed utxo - continue; - var script; - if (!rs || !rs.length) //legacy script - script = utxos[i].script; - else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') { - //redeemScript for segwit/bech32 and multisig (bech32) - let s = coinjs.script(); - s.writeBytes(Crypto.util.hexToBytes(rs)); - s.writeOp(0); - s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0), 8)); - script = Crypto.util.bytesToHex(s.buffer); - } else //redeemScript for multisig (segwit) - script = rs; - tx.addinput(utxos[i].tx_hash_big_endian, utxos[i].tx_output_n, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee - //update track values - rec_args.input_size += size_per_input; - rec_args.input_amount += util.Sat_to_BTC(utxos[i].value); - required_amount -= util.Sat_to_BTC(utxos[i].value); - if (fee_rate) //automatic fee calculation (dynamic) - required_amount += size_per_input * fee_rate; - } - rec_args.n += 1; - addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args) - .then(result => resolve(result)) - .catch(error => reject(error)) - }).catch(error => reject(error)) - }) - } - - function addOutputs(tx, receivers, amounts, change_address) { - let size = 0; - for (let i in receivers) { - tx.addoutput(receivers[i], amounts[i]); - size += _sizePerOutput(receivers[i]); - } - tx.addoutput(change_address, 0); - size += _sizePerOutput(change_address); - return size; - } - - /* - function autoFeeCalc(tx) { - return new Promise((resolve, reject) => { - get_fee_rate().then(fee_rate => { - let tx_size = tx.size(); - for (var i = 0; i < this.ins.length; i++) - switch (tx.extractScriptKey(i).type) { - case 'scriptpubkey': - tx_size += SIGN_SIZE; - break; - case 'segwit': - case 'multisig': - tx_size += SIGN_SIZE * 0.25; - break; - default: - console.warn('Unknown script-type'); - tx_size += SIGN_SIZE; - } - resolve(tx_size * fee_rate); - }).catch(error => reject(error)) - }) - } - - function editFee(tx, current_fee, target_fee, index = -1) { - //values are in satoshi - index = parseInt(index >= 0 ? index : tx.outs.length - index); - if (index < 0 || index >= tx.outs.length) - throw "Invalid index"; - let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places - current_value = tx.outs[index].value; //could be BigInterger - if (edit_value < 0 && edit_value > current_value) - throw "Insufficient value at vout"; - tx.outs[index].value = current_value instanceof BigInteger ? - current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value); - } - */ - - btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { - return new Promise((resolve, reject) => { - createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => { - debugger; - broadcastTx(result.transaction.serialize()) - .then(txid => resolve(txid)) - .catch(error => reject(error)); - }).catch(error => reject(error)) - }) - } - - const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { - return new Promise((resolve, reject) => { - try { - ({ - senders, - privkeys, - receivers, - amounts - } = validateTxParameters({ - senders, - privkeys, - receivers, - amounts, - fee, - change_address: options.change_address - })); - } catch (e) { - return reject(e) - } - let redeemScripts = [], - wif_keys = []; - for (let i in senders) { - let rs = _redeemScript(senders[i], privkeys[i]); //get redeem-script (segwit/bech32) - redeemScripts.push(rs); - rs === false ? wif_keys.unshift(privkeys[i]) : wif_keys.push(privkeys[i]); //sorting private-keys (wif) - } - if (redeemScripts.includes(null)) //TODO: segwit - return reject("Unable to get redeem-script"); - //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { - let tx = result.transaction; - console.debug("Unsigned:", tx.serialize()); - new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/))); //Sign the tx using private key WIF - console.debug("Signed:", tx.serialize()); - resolve(result); - }).catch(error => reject(error)); - }) - } - - btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) { - return new Promise((resolve, reject) => { - try { - ({ - senders, - receivers, - amounts - } = validateTxParameters({ - senders, - receivers, - amounts, - fee, - change_address: options.change_address - })); - } catch (e) { - return reject(e) - } - let redeemScripts = senders.map(id => _redeemScript(id)); - if (redeemScripts.includes(null)) //TODO: segwit - return reject("Unable to get redeem-script"); - //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { - result.tx_hex = result.transaction.serialize(); - delete result.transaction; - resolve(result); - }).catch(error => reject(error)) - }) - } - - btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) { - return new Promise((resolve, reject) => { - //validate tx parameters - let addr_type = validateAddress(sender); - if (!(["multisig", "multisigBech32"].includes(addr_type))) - return reject("Invalid sender (multisig):" + sender); - else { - let script = coinjs.script(); - let decode = (addr_type == "multisig") ? - script.decodeRedeemScript(redeemScript) : - script.decodeRedeemScriptBech32(redeemScript); - if (!decode || decode.address !== sender) - return reject("Invalid redeem-script"); - } - try { - ({ - receivers, - amounts - } = validateTxParameters({ - receivers, - amounts, - fee, - change_address: options.change_address - })); - } catch (e) { - return reject(e) - } - //create transaction - createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => { - result.tx_hex = result.transaction.serialize(); - delete result.transaction; - resolve(result); - }).catch(error => reject(error)) - - }) - } - - function deserializeTx(tx) { - if (typeof tx === 'string' || Array.isArray(tx)) { - try { - tx = coinjs.transaction().deserialize(tx); - } catch { - throw "Invalid transaction hex"; - } - } else if (typeof tx !== 'object' || typeof tx.sign !== 'function') - throw "Invalid transaction object"; - return tx; - } - - btcOperator.signTx = function (tx, privkeys, sighashtype = 1) { - tx = deserializeTx(tx); - if (!Array.isArray(privkeys)) - privkeys = [privkeys]; - for (let i in privkeys) - if (privkeys[i].length === 64) - privkeys[i] = coinjs.privkey2wif(privkeys[i]); - new Set(privkeys).forEach(key => tx.sign(key, sighashtype)); //Sign the tx using private key WIF - return tx.serialize(); - } - - const checkSigned = btcOperator.checkSigned = function (tx, bool = true) { - tx = deserializeTx(tx); - let n = []; - for (let i in tx.ins) { - var s = tx.extractScriptKey(i); - if (s['type'] !== 'multisig' && s['type'] !== 'multisig_bech32') - n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2)) - else { - var rs = coinjs.script().decodeRedeemScript(s.script); //will work for bech32 too, as only address is diff - let x = { - s: s['signatures'], - r: rs['signaturesRequired'], - t: rs['pubkeys'].length - }; - 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; - } - - btcOperator.checkIfSameTx = function (tx1, tx2) { - tx1 = deserializeTx(tx1); - tx2 = deserializeTx(tx2); - //compare input and output length - if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length) - return false; - //compare inputs - for (let i = 0; i < tx1.ins.length; i++) - if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index) - return false; - //compare outputs - for (let i = 0; i < tx1.outs.length; i++) - if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer)) - return false; - return true; - } - - const getTxOutput = (txid, i) => new Promise((resolve, reject) => { - fetch_api(`rawtx/${txid}`) - .then(result => resolve(result.out[i])) - .catch(error => reject(error)) - }); - - btcOperator.parseTransaction = function (tx) { - return new Promise((resolve, reject) => { - tx = deserializeTx(tx); - let result = {}; - let promises = []; - //Parse Inputs - for (let i = 0; i < tx.ins.length; i++) - promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index)); - Promise.all(promises).then(inputs => { - result.inputs = inputs.map(inp => Object({ - address: inp.addr, - value: util.Sat_to_BTC(inp.value) - })); - let signed = checkSigned(tx, false); - result.inputs.forEach((inp, i) => inp.signed = signed[i]); - //Parse Outputs - result.outputs = tx.outs.map(out => { - var address; - switch (out.script.chunks[0]) { - case 0: //bech32, multisig-bech32 - address = util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp); - break; - case 169: //segwit, multisig-segwit - address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig); - break; - case 118: //legacy - address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub); - } - return { - address, - value: util.Sat_to_BTC(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)); - resolve(result); - }).catch(error => reject(error)) - }) - } - - btcOperator.transactionID = function (tx) { - tx = deserializeTx(tx); - let clone = coinjs.clone(tx); - clone.witness = null; - 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 getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => { - fetch_api(`q/getblockcount`) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - - btcOperator.getTx = txid => new Promise((resolve, reject) => { - fetch_api(`rawtx/${txid}`).then(result => { - getLatestBlock().then(latest_block => resolve({ - block: result.block_height, - txid: result.hash, - time: result.time * 1000, - confirmations: result.block_height === null ? 0 : latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it - size: result.size, - fee: util.Sat_to_BTC(result.fee), - inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })), - total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)), - outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })), - total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)), - })) - }).catch(error => reject(error)) - }); - - btcOperator.getTx.hex = txid => new Promise((resolve, reject) => { - fetch_api(`rawtx/${txid}?format=hex`, false) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - - btcOperator.getAddressData = address => new Promise((resolve, reject) => { - fetch_api(`rawaddr/${address}`).then(data => { - let details = {}; - details.balance = util.Sat_to_BTC(data.final_balance); - details.address = data.address; - details.txs = data.txs.map(tx => { - let d = { - txid: tx.hash, - time: tx.time * 1000, //s to ms - block: tx.block_height, - } - //sender list - d.tx_senders = {}; - tx.inputs.forEach(i => { - if (i.prev_out.addr in d.tx_senders) - d.tx_senders[i.prev_out.addr] += i.prev_out.value; - else d.tx_senders[i.prev_out.addr] = i.prev_out.value; - }); - d.tx_input_value = 0; - for (let s in d.tx_senders) { - let val = d.tx_senders[s]; - d.tx_senders[s] = util.Sat_to_BTC(val); - d.tx_input_value += val; - } - d.tx_input_value = util.Sat_to_BTC(d.tx_input_value); - //receiver list - d.tx_receivers = {}; - tx.out.forEach(o => { - if (o.addr in d.tx_receivers) - d.tx_receivers[o.addr] += o.value; - else d.tx_receivers[o.addr] = o.value; - }); - d.tx_output_value = 0; - for (let r in d.tx_receivers) { - let val = d.tx_receivers[r]; - d.tx_receivers[r] = util.Sat_to_BTC(val); - d.tx_output_value += val; - } - d.tx_output_value = util.Sat_to_BTC(d.tx_output_value); - d.tx_fee = util.Sat_to_BTC(tx.fee); - //tx type - if (tx.result > 0) { //net > 0, balance inc => type=in - d.type = "in"; - d.amount = util.Sat_to_BTC(tx.result); - d.sender = Object.keys(d.tx_senders).filter(s => s !== address); - } else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out - d.type = "out"; - d.amount = util.Sat_to_BTC(tx.result * -1); - d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address); - d.fee = d.tx_fee; - } else { //net < 0 (fee) & no other id in receiver list => type=self - d.type = "self"; - d.amount = d.tx_receivers[address]; - d.address = address - } - return d; - }) - resolve(details); - }).catch(error => reject(error)) - }); - - btcOperator.getBlock = block => new Promise((resolve, reject) => { - fetch_api(`rawblock/${block}`).then(result => resolve({ - height: result.height, - hash: result.hash, - merkle_root: result.mrkl_root, - prev_block: result.prev_block, - next_block: result.next_block[0], - size: result.size, - time: result.time * 1000, //s to ms - txs: result.tx.map(t => Object({ - fee: t.fee, - size: t.size, - inputs: t.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })), - total_input_value: util.Sat_to_BTC(t.inputs.reduce((a, i) => a + i.prev_out.value, 0)), - outputs: t.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })), - total_output_value: util.Sat_to_BTC(t.out.reduce((a, o) => a += o.value, 0)), - })) - - })).catch(error => reject(error)) - }); - -})('object' === typeof module ? module.exports : window.btcOperator = {}); diff --git a/floCloudAPI.js b/floCloudAPI.js deleted file mode 100644 index c2d2c3f..0000000 --- a/floCloudAPI.js +++ /dev/null @@ -1,1055 +0,0 @@ -(function (EXPORTS) { //floCloudAPI v2.4.3a - /* FLO Cloud operations to send/request application data*/ - 'use strict'; - const floCloudAPI = EXPORTS; - - const DEFAULT = { - blockchainPrefix: 0x23, //Prefix version for FLO blockchain - SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk", - adminID: floGlobals.adminID, - application: floGlobals.application, - SNStorageName: "SuperNodeStorage", - callback: (d, e) => console.debug(d, e) - }; - - var user_id, user_public, user_private, aes_key; - - function user(id, priv) { - if (!priv || !id) - return user.clear(); - let pub = floCrypto.getPubKeyHex(priv); - if (!pub || !floCrypto.verifyPubKey(pub, id)) - return user.clear(); - let n = floCrypto.randInt(12, 20); - aes_key = floCrypto.randString(n); - user_private = Crypto.AES.encrypt(priv, aes_key); - user_public = pub; - user_id = id; - return user_id; - } - - Object.defineProperties(user, { - id: { - get: () => { - if (!user_id) - throw "User not set"; - return user_id; - } - }, - public: { - get: () => { - if (!user_public) - throw "User not set"; - return user_public; - } - }, - sign: { - value: msg => { - if (!user_private) - throw "User not set"; - return floCrypto.signData(msg, Crypto.AES.decrypt(user_private, aes_key)); - } - }, - clear: { - value: () => user_id = user_public = user_private = aes_key = undefined - } - }) - - Object.defineProperties(floCloudAPI, { - SNStorageID: { - get: () => DEFAULT.SNStorageID - }, - SNStorageName: { - get: () => DEFAULT.SNStorageName - }, - adminID: { - get: () => DEFAULT.adminID - }, - application: { - get: () => DEFAULT.application - }, - user: { - get: () => user - } - }); - - var appObjects, generalData, lastVC; - Object.defineProperties(floGlobals, { - appObjects: { - get: () => appObjects, - set: obj => appObjects = obj - }, - generalData: { - get: () => generalData, - set: data => generalData = data - }, - generalDataset: { - value: (type, options = {}) => generalData[filterKey(type, options)] - }, - lastVC: { - get: () => lastVC, - set: vc => lastVC = vc - } - }); - - var supernodes = {}; //each supnernode must be stored as floID : {uri:,pubKey:} - Object.defineProperty(floCloudAPI, 'nodes', { - get: () => JSON.parse(JSON.stringify(supernodes)) - }); - - var kBucket; - const K_Bucket = floCloudAPI.K_Bucket = function (masterID, nodeList) { - - const decodeID = floID => { - let k = bitjs.Base58.decode(floID); - k.shift(); - k.splice(-4, 4); - let decodedId = Crypto.util.bytesToHex(k); - let nodeIdBigInt = new BigInteger(decodedId, 16); - let nodeIdBytes = nodeIdBigInt.toByteArrayUnsigned(); - let nodeIdNewInt8Array = new Uint8Array(nodeIdBytes); - return nodeIdNewInt8Array; - }; - - const _KB = new BuildKBucket({ - localNodeId: decodeID(masterID) - }); - nodeList.forEach(id => _KB.add({ - id: decodeID(id), - floID: id - })); - - const _CO = nodeList.map(id => [_KB.distance(_KB.localNodeId, decodeID(id)), id]) - .sort((a, b) => a[0] - b[0]) - .map(a => a[1]); - - const self = this; - Object.defineProperty(self, 'tree', { - get: () => _KB - }); - Object.defineProperty(self, 'list', { - get: () => Array.from(_CO) - }); - - self.isNode = floID => _CO.includes(floID); - self.innerNodes = function (id1, id2) { - if (!_CO.includes(id1) || !_CO.includes(id2)) - throw Error('Given nodes are not supernode'); - let iNodes = [] - for (let i = _CO.indexOf(id1) + 1; _CO[i] != id2; i++) { - if (i < _CO.length) - iNodes.push(_CO[i]) - else i = -1 - } - return iNodes - } - self.outterNodes = function (id1, id2) { - if (!_CO.includes(id1) || !_CO.includes(id2)) - throw Error('Given nodes are not supernode'); - let oNodes = [] - for (let i = _CO.indexOf(id2) + 1; _CO[i] != id1; i++) { - if (i < _CO.length) - oNodes.push(_CO[i]) - else i = -1 - } - return oNodes - } - self.prevNode = function (id, N = 1) { - let n = N || _CO.length; - if (!_CO.includes(id)) - throw Error('Given node is not supernode'); - let pNodes = [] - for (let i = 0, j = _CO.indexOf(id) - 1; i < n; j--) { - if (j == _CO.indexOf(id)) - break; - else if (j > -1) - pNodes[i++] = _CO[j] - else j = _CO.length - } - return (N == 1 ? pNodes[0] : pNodes) - } - self.nextNode = function (id, N = 1) { - let n = N || _CO.length; - if (!_CO.includes(id)) - throw Error('Given node is not supernode'); - if (!n) n = _CO.length; - let nNodes = [] - for (let i = 0, j = _CO.indexOf(id) + 1; i < n; j++) { - if (j == _CO.indexOf(id)) - break; - else if (j < _CO.length) - nNodes[i++] = _CO[j] - else j = -1 - } - return (N == 1 ? nNodes[0] : nNodes) - } - self.closestNode = function (id, N = 1) { - let decodedId = decodeID(id); - let n = N || _CO.length; - let cNodes = _KB.closest(decodedId, n) - .map(k => k.floID) - return (N == 1 ? cNodes[0] : cNodes) - } - } - - floCloudAPI.init = function startCloudProcess(nodes) { - return new Promise((resolve, reject) => { - try { - supernodes = nodes; - kBucket = new K_Bucket(DEFAULT.SNStorageID, Object.keys(supernodes)); - resolve('Cloud init successful'); - } catch (error) { - reject(error); - } - }) - } - - Object.defineProperty(floCloudAPI, 'kBucket', { - get: () => kBucket - }); - - const _inactive = new Set(); - - function ws_connect(snID) { - return new Promise((resolve, reject) => { - if (!(snID in supernodes)) - return reject(`${snID} is not a supernode`) - if (_inactive.has(snID)) - return reject(`${snID} is not active`) - var wsConn = new WebSocket("wss://" + supernodes[snID].uri + "/"); - wsConn.onopen = evt => resolve(wsConn); - wsConn.onerror = evt => { - _inactive.add(snID) - reject(`${snID} is unavailable`) - } - }) - } - - function ws_activeConnect(snID, reverse = false) { - return new Promise((resolve, reject) => { - if (_inactive.size === kBucket.list.length) - return reject('Cloud offline'); - if (!(snID in supernodes)) - snID = kBucket.closestNode(proxyID(snID)); - ws_connect(snID) - .then(node => resolve(node)) - .catch(error => { - if (reverse) - var nxtNode = kBucket.prevNode(snID); - else - var nxtNode = kBucket.nextNode(snID); - ws_activeConnect(nxtNode, reverse) - .then(node => resolve(node)) - .catch(error => reject(error)) - }) - }) - } - - function fetch_API(snID, data) { - return new Promise((resolve, reject) => { - if (_inactive.has(snID)) - return reject(`${snID} is not active`); - let fetcher, sn_url = "https://" + supernodes[snID].uri; - if (typeof data === "string") - fetcher = fetch(sn_url + "?" + data); - else if (typeof data === "object" && data.method === "POST") - fetcher = fetch(sn_url, data); - fetcher.then(response => { - if (response.ok || response.status === 400 || response.status === 500) - resolve(response); - else - reject(response); - }).catch(error => reject(error)) - }) - } - - function fetch_ActiveAPI(snID, data, reverse = false) { - return new Promise((resolve, reject) => { - if (_inactive.size === kBucket.list.length) - return reject('Cloud offline'); - if (!(snID in supernodes)) - snID = kBucket.closestNode(proxyID(snID)); - fetch_API(snID, data) - .then(result => resolve(result)) - .catch(error => { - _inactive.add(snID) - if (reverse) - var nxtNode = kBucket.prevNode(snID); - else - var nxtNode = kBucket.nextNode(snID); - fetch_ActiveAPI(nxtNode, data, reverse) - .then(result => resolve(result)) - .catch(error => reject(error)); - }) - }) - } - - function singleRequest(floID, data_obj, method = "POST") { - return new Promise((resolve, reject) => { - let data; - if (method === "POST") - data = { - method: "POST", - body: JSON.stringify(data_obj) - }; - else - data = new URLSearchParams(JSON.parse(JSON.stringify(data_obj))).toString(); - fetch_ActiveAPI(floID, data).then(response => { - if (response.ok) - response.json() - .then(result => resolve(result)) - .catch(error => reject(error)) - else response.text() - .then(result => reject(response.status + ": " + result)) //Error Message from Node - .catch(error => reject(error)) - }).catch(error => reject(error)) - }) - } - - const _liveRequest = {}; - - function liveRequest(floID, request, callback) { - const filterData = typeof request.status !== 'undefined' ? - data => { - if (request.status) - return data; - else { - let filtered = {}; - for (let i in data) - if (request.trackList.includes(i)) - filtered[i] = data[i]; - return filtered; - } - } : - data => { - data = objectifier(data); - let filtered = {}, - proxy = proxyID(request.receiverID), - r = request; - for (let v in data) { - let d = data[v]; - if ((!r.atVectorClock || r.atVectorClock == v) && - (r.atVectorClock || !r.lowerVectorClock || r.lowerVectorClock <= v) && - (r.atVectorClock || !r.upperVectorClock || r.upperVectorClock >= v) && - (!r.afterTime || r.afterTime < d.log_time) && - r.application == d.application && - (proxy == d.receiverID || proxy == d.proxyID) && - (!r.comment || r.comment == d.comment) && - (!r.type || r.type == d.type) && - (!r.senderID || r.senderID.includes(d.senderID))) - filtered[v] = data[v]; - } - return filtered; - }; - - return new Promise((resolve, reject) => { - ws_activeConnect(floID).then(node => { - let randID = floCrypto.randString(5); - node.send(JSON.stringify(request)); - node.onmessage = (evt) => { - let d = null, - e = null; - try { - d = filterData(JSON.parse(evt.data)); - } catch (error) { - e = evt.data - } finally { - callback(d, e) - } - } - _liveRequest[randID] = node; - _liveRequest[randID].request = request; - resolve(randID); - }).catch(error => reject(error)); - }); - } - - Object.defineProperty(floCloudAPI, 'liveRequest', { - get: () => _liveRequest - }); - - Object.defineProperty(floCloudAPI, 'inactive', { - get: () => _inactive - }); - - const util = floCloudAPI.util = {}; - - const encodeMessage = util.encodeMessage = function (message) { - return btoa(unescape(encodeURIComponent(JSON.stringify(message)))) - } - - const decodeMessage = util.decodeMessage = function (message) { - return JSON.parse(decodeURIComponent(escape(atob(message)))) - } - - const filterKey = util.filterKey = function (type, options = {}) { - return type + (options.comment ? ':' + options.comment : '') + - '|' + (options.group || options.receiverID || DEFAULT.adminID) + - '|' + (options.application || DEFAULT.application); - } - - const proxyID = util.proxyID = function (address) { - if (!address) - return; - var bytes; - if (address.length == 33 || address.length == 34) { //legacy encoding - let decode = bitjs.Base58.decode(address); - bytes = decode.slice(0, decode.length - 4); - let checksum = decode.slice(decode.length - 4), - hash = Crypto.SHA256(Crypto.SHA256(bytes, { - asBytes: true - }), { - asBytes: true - }); - hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3] ? - bytes = undefined : bytes.shift(); - } else if (address.length == 42 || address.length == 62) { //bech encoding - if (typeof coinjs !== 'function') - throw "library missing (lib_btc.js)"; - let decode = coinjs.bech32_decode(address); - if (decode) { - bytes = decode.data; - bytes.shift(); - bytes = coinjs.bech32_convert(bytes, 5, 8, false); - if (address.length == 62) //for long bech, aggregate once more to get 160 bit - bytes = coinjs.bech32_convert(bytes, 5, 8, false); - } - } else if (address.length == 66) { //public key hex - bytes = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(address), { - asBytes: true - })); - } - if (!bytes) - throw "Invalid address: " + address; - else { - bytes.unshift(DEFAULT.blockchainPrefix); - let hash = Crypto.SHA256(Crypto.SHA256(bytes, { - asBytes: true - }), { - asBytes: true - }); - return bitjs.Base58.encode(bytes.concat(hash.slice(0, 4))); - } - } - - const lastCommit = {}; - Object.defineProperty(lastCommit, 'get', { - value: objName => JSON.parse(lastCommit[objName]) - }); - Object.defineProperty(lastCommit, 'set', { - value: objName => lastCommit[objName] = JSON.stringify(appObjects[objName]) - }); - - function updateObject(objectName, dataSet) { - try { - console.log(dataSet) - let vcList = Object.keys(dataSet).sort(); - for (let vc of vcList) { - if (vc < lastVC[objectName] || dataSet[vc].type !== objectName) - continue; - switch (dataSet[vc].comment) { - case "RESET": - if (dataSet[vc].message.reset) - appObjects[objectName] = dataSet[vc].message.reset; - break; - case "UPDATE": - if (dataSet[vc].message.diff) - appObjects[objectName] = diff.merge(appObjects[objectName], dataSet[vc].message.diff); - } - lastVC[objectName] = vc; - } - lastCommit.set(objectName); - compactIDB.writeData("appObjects", appObjects[objectName], objectName); - compactIDB.writeData("lastVC", lastVC[objectName], objectName); - } catch (error) { - console.error(error) - } - } - - function storeGeneral(fk, dataSet) { - try { - console.log(dataSet) - if (typeof generalData[fk] !== "object") - generalData[fk] = {} - for (let vc in dataSet) { - generalData[fk][vc] = dataSet[vc]; - if (dataSet[vc].log_time > lastVC[fk]) - lastVC[fk] = dataSet[vc].log_time; - } - compactIDB.writeData("lastVC", lastVC[fk], fk) - compactIDB.writeData("generalData", generalData[fk], fk) - } catch (error) { - console.error(error) - } - } - - function objectifier(data) { - if (!Array.isArray(data)) - data = [data]; - return Object.fromEntries(data.map(d => { - d.message = decodeMessage(d.message); - return [d.vectorClock, d]; - })); - } - - //set status as online for user_id - floCloudAPI.setStatus = function (options = {}) { - return new Promise((resolve, reject) => { - let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback; - var request = { - floID: user.id, - application: options.application || DEFAULT.application, - time: Date.now(), - status: true, - pubKey: user.public - } - let hashcontent = ["time", "application", "floID"].map(d => request[d]).join("|"); - request.sign = user.sign(hashcontent); - liveRequest(options.refID || DEFAULT.adminID, request, callback) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - //request status of floID(s) in trackList - floCloudAPI.requestStatus = function (trackList, options = {}) { - return new Promise((resolve, reject) => { - if (!Array.isArray(trackList)) - trackList = [trackList]; - let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback; - let request = { - status: false, - application: options.application || DEFAULT.application, - trackList: trackList - } - liveRequest(options.refID || DEFAULT.adminID, request, callback) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - //send any message to supernode cloud storage - const sendApplicationData = floCloudAPI.sendApplicationData = function (message, type, options = {}) { - return new Promise((resolve, reject) => { - var data = { - senderID: user.id, - receiverID: options.receiverID || DEFAULT.adminID, - pubKey: user.public, - message: encodeMessage(message), - time: Date.now(), - application: options.application || DEFAULT.application, - type: type, - comment: options.comment || "" - } - let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"] - .map(d => data[d]).join("|") - data.sign = user.sign(hashcontent); - singleRequest(data.receiverID, data) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - //request any data from supernode cloud - const requestApplicationData = floCloudAPI.requestApplicationData = function (type, options = {}) { - return new Promise((resolve, reject) => { - var request = { - receiverID: options.receiverID || DEFAULT.adminID, - senderID: options.senderID || undefined, - application: options.application || DEFAULT.application, - type: type, - comment: options.comment || undefined, - lowerVectorClock: options.lowerVectorClock || undefined, - upperVectorClock: options.upperVectorClock || undefined, - atVectorClock: options.atVectorClock || undefined, - afterTime: options.afterTime || undefined, - mostRecent: options.mostRecent || undefined, - } - - if (options.callback instanceof Function) { - liveRequest(request.receiverID, request, options.callback) - .then(result => resolve(result)) - .catch(error => reject(error)) - } else { - if (options.method === "POST") - request = { - time: Date.now(), - request - }; - singleRequest(request.receiverID, request, options.method || "GET") - .then(data => resolve(data)).catch(error => reject(error)) - } - }) - } - - /*(NEEDS UPDATE) - //delete data from supernode cloud (received only) - floCloudAPI.deleteApplicationData = function(vectorClocks, options = {}) { - return new Promise((resolve, reject) => { - var delreq = { - requestorID: user.id, - pubKey: user.public, - time: Date.now(), - delete: (Array.isArray(vectorClocks) ? vectorClocks : [vectorClocks]), - application: options.application || DEFAULT.application - } - let hashcontent = ["time", "application", "delete"] - .map(d => delreq[d]).join("|") - delreq.sign = user.sign(hashcontent) - singleRequest(delreq.requestorID, delreq).then(result => { - let success = [], - failed = []; - result.forEach(r => r.status === 'fulfilled' ? - success.push(r.value) : failed.push(r.reason)); - resolve({ - success, - failed - }) - }).catch(error => reject(error)) - }) - } - */ - /*(NEEDS UPDATE) - //edit comment of data in supernode cloud (mutable comments only) - floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, options = {}) { - return new Promise((resolve, reject) => { - let p0 - if (!oldData) { - options.atVectorClock = vectorClock; - options.callback = false; - p0 = requestApplicationData(false, options) - } else - p0 = Promise.resolve({ - vectorClock: { - ...oldData - } - }) - p0.then(d => { - if (d.senderID != user.id) - return reject("Invalid requestorID") - else if (!d.comment.startsWith("EDIT:")) - return reject("Data immutable") - let data = { - requestorID: user.id, - receiverID: d.receiverID, - time: Date.now(), - application: d.application, - edit: { - vectorClock: vectorClock, - comment: newComment - } - } - d.comment = data.edit.comment; - let hashcontent = ["receiverID", "time", "application", "type", "message", - "comment" - ] - .map(x => d[x]).join("|") - data.edit.sign = user.sign(hashcontent) - singleRequest(data.receiverID, data) - .then(result => resolve("Data comment updated")) - .catch(error => reject(error)) - }) - }) - } - */ - - //tag data in supernode cloud (subAdmin access only) - floCloudAPI.tagApplicationData = function (vectorClock, tag, options = {}) { - return new Promise((resolve, reject) => { - if (!floGlobals.subAdmins.includes(user.id)) - return reject("Only subAdmins can tag data") - var request = { - receiverID: options.receiverID || DEFAULT.adminID, - requestorID: user.id, - pubKey: user.public, - time: Date.now(), - vectorClock: vectorClock, - tag: tag, - } - let hashcontent = ["time", "vectorClock", 'tag'].map(d => request[d]).join("|"); - request.sign = user.sign(hashcontent); - singleRequest(request.receiverID, request) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - //note data in supernode cloud (receiver only or subAdmin allowed if receiver is adminID) - floCloudAPI.noteApplicationData = function (vectorClock, note, options = {}) { - return new Promise((resolve, reject) => { - var request = { - receiverID: options.receiverID || DEFAULT.adminID, - requestorID: user.id, - pubKey: user.public, - time: Date.now(), - vectorClock: vectorClock, - note: note, - } - let hashcontent = ["time", "vectorClock", 'note'].map(d => request[d]).join("|"); - request.sign = user.sign(hashcontent); - singleRequest(request.receiverID, request) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - //send general data - floCloudAPI.sendGeneralData = function (message, type, options = {}) { - return new Promise((resolve, reject) => { - if (options.encrypt) { - let encryptionKey = options.encrypt === true ? - floGlobals.settings.encryptionKey : options.encrypt - message = floCrypto.encryptData(JSON.stringify(message), encryptionKey) - } - sendApplicationData(message, type, options) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - //request general data - floCloudAPI.requestGeneralData = function (type, options = {}) { - return new Promise((resolve, reject) => { - var fk = filterKey(type, options) - lastVC[fk] = parseInt(lastVC[fk]) || 0; - options.afterTime = options.afterTime || lastVC[fk]; - if (options.callback instanceof Function) { - let new_options = Object.create(options) - new_options.callback = (d, e) => { - storeGeneral(fk, d); - options.callback(d, e) - } - requestApplicationData(type, new_options) - .then(result => resolve(result)) - .catch(error => reject(error)) - } else { - requestApplicationData(type, options).then(dataSet => { - storeGeneral(fk, objectifier(dataSet)) - resolve(dataSet) - }).catch(error => reject(error)) - } - }) - } - - //request an object data from supernode cloud - floCloudAPI.requestObjectData = function (objectName, options = {}) { - return new Promise((resolve, reject) => { - options.lowerVectorClock = options.lowerVectorClock || lastVC[objectName] + 1; - options.senderID = [false, null].includes(options.senderID) ? null : - options.senderID || floGlobals.subAdmins; - options.mostRecent = true; - options.comment = 'RESET'; - let callback = null; - if (options.callback instanceof Function) { - let old_callback = options.callback; - callback = (d, e) => { - updateObject(objectName, d); - old_callback(d, e); - } - delete options.callback; - } - requestApplicationData(objectName, options).then(dataSet => { - updateObject(objectName, objectifier(dataSet)); - delete options.comment; - options.lowerVectorClock = lastVC[objectName] + 1; - delete options.mostRecent; - if (callback) { - let new_options = Object.create(options); - new_options.callback = callback; - requestApplicationData(objectName, new_options) - .then(result => resolve(result)) - .catch(error => reject(error)) - } else { - requestApplicationData(objectName, options).then(dataSet => { - updateObject(objectName, objectifier(dataSet)) - resolve(appObjects[objectName]) - }).catch(error => reject(error)) - } - }).catch(error => reject(error)) - }) - } - - floCloudAPI.closeRequest = function (requestID) { - return new Promise((resolve, reject) => { - let conn = _liveRequest[requestID] - if (!conn) - return reject('Request not found') - conn.onclose = evt => { - delete _liveRequest[requestID]; - resolve('Request connection closed') - } - conn.close() - }) - } - - //reset or initialize an object and send it to cloud - floCloudAPI.resetObjectData = function (objectName, options = {}) { - return new Promise((resolve, reject) => { - let message = { - reset: appObjects[objectName] - } - options.comment = 'RESET'; - sendApplicationData(message, objectName, options).then(result => { - lastCommit.set(objectName); - resolve(result) - }).catch(error => reject(error)) - }) - } - - //update the diff and send it to cloud - floCloudAPI.updateObjectData = function (objectName, options = {}) { - return new Promise((resolve, reject) => { - let message = { - diff: diff.find(lastCommit.get(objectName), appObjects[ - objectName]) - } - options.comment = 'UPDATE'; - sendApplicationData(message, objectName, options).then(result => { - lastCommit.set(objectName); - resolve(result) - }).catch(error => reject(error)) - }) - } - - /* - Functions: - findDiff(original, updatedObj) returns an object with the added, deleted and updated differences - mergeDiff(original, allDiff) returns a new object from original object merged with all differences (allDiff is returned object of findDiff) - */ - var diff = (function () { - const isDate = d => d instanceof Date; - const isEmpty = o => Object.keys(o).length === 0; - const isObject = o => o != null && typeof o === 'object'; - const properObject = o => isObject(o) && !o.hasOwnProperty ? { - ...o - } : o; - const getLargerArray = (l, r) => l.length > r.length ? l : r; - - const preserve = (diff, left, right) => { - if (!isObject(diff)) return diff; - return Object.keys(diff).reduce((acc, key) => { - const leftArray = left[key]; - const rightArray = right[key]; - if (Array.isArray(leftArray) && Array.isArray(rightArray)) { - const array = [...getLargerArray(leftArray, rightArray)]; - return { - ...acc, - [key]: array.reduce((acc2, item, index) => { - if (diff[key].hasOwnProperty(index)) { - acc2[index] = preserve(diff[key][index], leftArray[index], rightArray[index]); // diff recurse and check for nested arrays - return acc2; - } - delete acc2[index]; // no diff aka empty - return acc2; - }, array) - }; - } - return { - ...acc, - [key]: diff[key] - }; - }, {}); - }; - - const updatedDiff = (lhs, rhs) => { - if (lhs === rhs) return {}; - if (!isObject(lhs) || !isObject(rhs)) return rhs; - const l = properObject(lhs); - const r = properObject(rhs); - if (isDate(l) || isDate(r)) { - if (l.valueOf() == r.valueOf()) return {}; - return r; - } - return Object.keys(r).reduce((acc, key) => { - if (l.hasOwnProperty(key)) { - const difference = updatedDiff(l[key], r[key]); - if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc; - return { - ...acc, - [key]: difference - }; - } - return acc; - }, {}); - }; - - - const diff = (lhs, rhs) => { - if (lhs === rhs) return {}; // equal return no diff - if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs - const l = properObject(lhs); - const r = properObject(rhs); - const deletedValues = Object.keys(l).reduce((acc, key) => { - return r.hasOwnProperty(key) ? acc : { - ...acc, - [key]: null - }; - }, {}); - if (isDate(l) || isDate(r)) { - if (l.valueOf() == r.valueOf()) return {}; - return r; - } - return Object.keys(r).reduce((acc, key) => { - if (!l.hasOwnProperty(key)) return { - ...acc, - [key]: r[key] - }; // return added r key - const difference = diff(l[key], r[key]); - if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc; // return no diff - return { - ...acc, - [key]: difference - }; // return updated key - }, deletedValues); - }; - - const addedDiff = (lhs, rhs) => { - if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {}; - const l = properObject(lhs); - const r = properObject(rhs); - return Object.keys(r).reduce((acc, key) => { - if (l.hasOwnProperty(key)) { - const difference = addedDiff(l[key], r[key]); - if (isObject(difference) && isEmpty(difference)) return acc; - return { - ...acc, - [key]: difference - }; - } - return { - ...acc, - [key]: r[key] - }; - }, {}); - }; - - const arrayDiff = (lhs, rhs) => { - if (lhs === rhs) return {}; // equal return no diff - if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs - const l = properObject(lhs); - const r = properObject(rhs); - const deletedValues = Object.keys(l).reduce((acc, key) => { - return r.hasOwnProperty(key) ? acc : { - ...acc, - [key]: null - }; - }, {}); - if (isDate(l) || isDate(r)) { - if (l.valueOf() == r.valueOf()) return {}; - return r; - } - if (Array.isArray(r) && Array.isArray(l)) { - const deletedValues = l.reduce((acc, item, index) => { - return r.hasOwnProperty(index) ? acc.concat(item) : acc.concat(null); - }, []); - return r.reduce((acc, rightItem, index) => { - if (!deletedValues.hasOwnProperty(index)) { - return acc.concat(rightItem); - } - const leftItem = l[index]; - const difference = diff(rightItem, leftItem); - if (isObject(difference) && isEmpty(difference) && !isDate(difference)) { - delete acc[index]; - return acc; // return no diff - } - return acc.slice(0, index).concat(rightItem).concat(acc.slice(index + 1)); // return updated key - }, deletedValues); - } - - return Object.keys(r).reduce((acc, key) => { - if (!l.hasOwnProperty(key)) return { - ...acc, - [key]: r[key] - }; // return added r key - const difference = diff(l[key], r[key]); - if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc; // return no diff - return { - ...acc, - [key]: difference - }; // return updated key - }, deletedValues); - }; - - const deletedDiff = (lhs, rhs) => { - if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {}; - const l = properObject(lhs); - const r = properObject(rhs); - return Object.keys(l).reduce((acc, key) => { - if (r.hasOwnProperty(key)) { - const difference = deletedDiff(l[key], r[key]); - if (isObject(difference) && isEmpty(difference)) return acc; - return { - ...acc, - [key]: difference - }; - } - return { - ...acc, - [key]: null - }; - }, {}); - }; - - const mergeRecursive = (obj1, obj2, deleteMode = false) => { - for (var p in obj2) { - try { - if (obj2[p].constructor == Object) - obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode); - // Property in destination object set; update its value. - else if (Array.isArray(obj2[p])) { - // obj1[p] = []; - if (obj2[p].length < 1) - obj1[p] = obj2[p]; - else - obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode); - } else - obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p]; - } catch (e) { - // Property in destination object not set; create it and set its value. - obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p]; - } - } - return obj1; - } - - const cleanse = (obj) => { - Object.keys(obj).forEach(key => { - var value = obj[key]; - if (typeof value === "object" && value !== null) - obj[key] = cleanse(value); - else if (typeof value === 'undefined') - delete obj[key]; // undefined, remove it - }); - if (Array.isArray(obj)) - obj = obj.filter(v => typeof v !== 'undefined'); - return obj; - } - - - const findDiff = (lhs, rhs) => ({ - added: addedDiff(lhs, rhs), - deleted: deletedDiff(lhs, rhs), - updated: updatedDiff(lhs, rhs), - }); - - /*obj is original object or array, diff is the output of findDiff */ - const mergeDiff = (obj, diff) => { - if (Object.keys(diff.updated).length !== 0) - obj = mergeRecursive(obj, diff.updated) - if (Object.keys(diff.deleted).length !== 0) { - obj = mergeRecursive(obj, diff.deleted, true) - obj = cleanse(obj) - } - if (Object.keys(diff.added).length !== 0) - obj = mergeRecursive(obj, diff.added) - return obj - } - - return { - find: findDiff, - merge: mergeDiff - } - })(); - - -})('object' === typeof module ? module.exports : window.floCloudAPI = {}); \ No newline at end of file diff --git a/floDapps.js b/floDapps.js deleted file mode 100644 index 4d525a2..0000000 --- a/floDapps.js +++ /dev/null @@ -1,845 +0,0 @@ -(function (EXPORTS) { //floDapps v2.4.0 - /* General functions for FLO Dapps*/ - 'use strict'; - const floDapps = EXPORTS; - - const DEFAULT = { - root: "floDapps", - application: floGlobals.application, - adminID: floGlobals.adminID - }; - - Object.defineProperties(floDapps, { - application: { - get: () => DEFAULT.application - }, - adminID: { - get: () => DEFAULT.adminID - }, - root: { - get: () => DEFAULT.root - } - }); - - var user_priv_raw, aes_key, user_priv_wrap; //private variable inside capsule - const raw_user = { - get private() { - if (!user_priv_raw) - throw "User not logged in"; - return Crypto.AES.decrypt(user_priv_raw, aes_key); - } - } - - var user_id, user_public, user_private; - const user = floDapps.user = { - get id() { - if (!user_id) - throw "User not logged in"; - return user_id; - }, - get public() { - if (!user_public) - throw "User not logged in"; - return user_public; - }, - get private() { - if (!user_private) - throw "User not logged in"; - else if (user_private instanceof Function) - return user_private(); - else - return Crypto.AES.decrypt(user_private, aes_key); - }, - sign(message) { - return floCrypto.signData(message, raw_user.private); - }, - decrypt(data) { - return floCrypto.decryptData(data, raw_user.private); - }, - encipher(message) { - return Crypto.AES.encrypt(message, raw_user.private); - }, - decipher(data) { - return Crypto.AES.decrypt(data, raw_user.private); - }, - get db_name() { - return "floDapps#" + floCrypto.toFloID(user.id); - }, - lock() { - user_private = user_priv_wrap; - }, - async unlock() { - if (await user.private === raw_user.private) - user_private = user_priv_raw; - }, - get_contact(id) { - if (!user.contacts) - throw "Contacts not available"; - else if (user.contacts[id]) - return user.contacts[id]; - else { - let id_raw = floCrypto.decodeAddr(id).hex; - for (let i in user.contacts) - if (floCrypto.decodeAddr(i).hex == id_raw) - return user.contacts[i]; - } - }, - get_pubKey(id) { - if (!user.pubKeys) - throw "Contacts not available"; - else if (user.pubKeys[id]) - return user.pubKeys[id]; - else { - let id_raw = floCrypto.decodeAddr(id).hex; - for (let i in user.pubKeys) - if (floCrypto.decodeAddr(i).hex == id_raw) - return user.pubKeys[i]; - } - }, - clear() { - user_id = user_public = user_private = undefined; - user_priv_raw = aes_key = undefined; - delete user.contacts; - delete user.pubKeys; - delete user.messages; - } - }; - - Object.defineProperties(window, { - myFloID: { - get: () => { - try { - return user.id; - } catch { - return; - } - } - }, - myUserID: { - get: () => { - try { - return user.id; - } catch { - return; - } - } - }, - myPubKey: { - get: () => { - try { - return user.public; - } catch { - return; - } - } - }, - myPrivKey: { - get: () => { - try { - return user.private; - } catch { - return; - } - } - } - }); - - var subAdmins, trustedIDs, settings; - Object.defineProperties(floGlobals, { - subAdmins: { - get: () => subAdmins - }, - trustedIDs: { - get: () => trustedIDs - }, - settings: { - get: () => settings - }, - contacts: { - get: () => user.contacts - }, - pubKeys: { - get: () => user.pubKeys - }, - messages: { - get: () => user.messages - } - }) - - function initIndexedDB() { - return new Promise((resolve, reject) => { - var obs_g = { - //general - lastTx: {}, - //supernode (cloud list) - supernodes: { - indexes: { - uri: null, - pubKey: null - } - } - } - var obs_a = { - //login credentials - credentials: {}, - //for Dapps - subAdmins: {}, - trustedIDs: {}, - settings: {}, - appObjects: {}, - generalData: {}, - lastVC: {} - } - //add other given objectStores - initIndexedDB.appObs = initIndexedDB.appObs || {} - for (let o in initIndexedDB.appObs) - if (!(o in obs_a)) - obs_a[o] = initIndexedDB.appObs[o] - Promise.all([ - compactIDB.initDB(DEFAULT.application, obs_a), - compactIDB.initDB(DEFAULT.root, obs_g) - ]).then(result => { - compactIDB.setDefaultDB(DEFAULT.application) - resolve("IndexedDB App Storage Initated Successfully") - }).catch(error => reject(error)); - }) - } - - function initUserDB() { - return new Promise((resolve, reject) => { - var obs = { - contacts: {}, - pubKeys: {}, - messages: {} - } - compactIDB.initDB(user.db_name, obs).then(result => { - resolve("UserDB Initated Successfully") - }).catch(error => reject('Init userDB failed')); - }) - } - - function loadUserDB() { - return new Promise((resolve, reject) => { - var loadData = ["contacts", "pubKeys", "messages"] - var promises = [] - for (var i = 0; i < loadData.length; i++) - promises[i] = compactIDB.readAllData(loadData[i], user.db_name) - Promise.all(promises).then(results => { - for (var i = 0; i < loadData.length; i++) - user[loadData[i]] = results[i] - resolve("Loaded Data from userDB") - }).catch(error => reject('Load userDB failed')) - }) - } - - const startUpOptions = { - cloud: true, - app_config: true, - } - - floDapps.startUpOptions = { - set app_config(val) { - if (val === true || val === false) - startUpOptions.app_config = val; - }, - get app_config() { return startUpOptions.app_config }, - - set cloud(val) { - if (val === true || val === false) - startUpOptions.cloud = val; - }, - get cloud() { return startUpOptions.cloud }, - } - - const startUpFunctions = []; - - startUpFunctions.push(function readSupernodeListFromAPI() { - return new Promise((resolve, reject) => { - if (!startUpOptions.cloud) - return resolve("No cloud for this app"); - compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => { - var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName }; - if (typeof lastTx == 'number') //lastTx is tx count (*backward support) - query_options.ignoreOld = lastTx; - else if (typeof lastTx == 'string') //lastTx is txid of last tx - query_options.after = lastTx; - //fetch data from flosight - floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => { - for (var i = result.data.length - 1; i >= 0; i--) { - var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName]; - for (let sn in content.removeNodes) - compactIDB.removeData("supernodes", sn, DEFAULT.root); - for (let sn in content.newNodes) - compactIDB.writeData("supernodes", content.newNodes[sn], sn, DEFAULT.root); - for (let sn in content.updateNodes) - compactIDB.readData("supernodes", sn, DEFAULT.root).then(r => { - r = r || {} - r.uri = content.updateNodes[sn]; - compactIDB.writeData("supernodes", r, sn, DEFAULT.root); - }); - } - compactIDB.writeData("lastTx", result.lastItem, floCloudAPI.SNStorageID, DEFAULT.root); - compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => { - floCloudAPI.init(nodes) - .then(result => resolve("Loaded Supernode list\n" + result)) - .catch(error => reject(error)) - }) - }) - }).catch(error => reject(error)) - }) - }); - - startUpFunctions.push(function readAppConfigFromAPI() { - return new Promise((resolve, reject) => { - if (!startUpOptions.app_config) - return resolve("No configs for this app"); - compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => { - var query_options = { sentOnly: true, pattern: DEFAULT.application }; - if (typeof lastTx == 'number') //lastTx is tx count (*backward support) - query_options.ignoreOld = lastTx; - else if (typeof lastTx == 'string') //lastTx is txid of last tx - query_options.after = lastTx; - //fetch data from flosight - floBlockchainAPI.readData(DEFAULT.adminID, query_options).then(result => { - for (var i = result.data.length - 1; i >= 0; i--) { - var content = JSON.parse(result.data[i])[DEFAULT.application]; - if (!content || typeof content !== "object") - continue; - if (Array.isArray(content.removeSubAdmin)) - for (var j = 0; j < content.removeSubAdmin.length; j++) - compactIDB.removeData("subAdmins", content.removeSubAdmin[j]); - if (Array.isArray(content.addSubAdmin)) - for (var k = 0; k < content.addSubAdmin.length; k++) - compactIDB.writeData("subAdmins", true, content.addSubAdmin[k]); - if (Array.isArray(content.removeTrustedID)) - for (var j = 0; j < content.removeTrustedID.length; j++) - compactIDB.removeData("trustedIDs", content.removeTrustedID[j]); - if (Array.isArray(content.addTrustedID)) - for (var k = 0; k < content.addTrustedID.length; k++) - compactIDB.writeData("trustedIDs", true, content.addTrustedID[k]); - if (content.settings) - for (let l in content.settings) - compactIDB.writeData("settings", content.settings[l], l) - } - compactIDB.writeData("lastTx", result.lastItem, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root); - compactIDB.readAllData("subAdmins").then(result => { - subAdmins = Object.keys(result); - compactIDB.readAllData("trustedIDs").then(result => { - trustedIDs = Object.keys(result); - compactIDB.readAllData("settings").then(result => { - settings = result; - resolve("Read app configuration from blockchain"); - }) - }) - }) - }) - }).catch(error => reject(error)) - }) - }); - - startUpFunctions.push(function loadDataFromAppIDB() { - return new Promise((resolve, reject) => { - if (!startUpOptions.cloud) - return resolve("No cloud for this app"); - var loadData = ["appObjects", "generalData", "lastVC"] - var promises = [] - for (var i = 0; i < loadData.length; i++) - promises[i] = compactIDB.readAllData(loadData[i]) - Promise.all(promises).then(results => { - for (var i = 0; i < loadData.length; i++) - floGlobals[loadData[i]] = results[i] - resolve("Loaded Data from app IDB") - }).catch(error => reject(error)) - }) - }); - - var keyInput = type => new Promise((resolve, reject) => { - let inputVal = prompt(`Enter ${type}: `) - if (inputVal === null) - reject(null) - else - resolve(inputVal) - }); - - function getCredentials() { - - const readSharesFromIDB = indexArr => new Promise((resolve, reject) => { - var promises = [] - for (var i = 0; i < indexArr.length; i++) - promises.push(compactIDB.readData('credentials', indexArr[i])) - Promise.all(promises).then(shares => { - var secret = floCrypto.retrieveShamirSecret(shares) - if (secret) - resolve(secret) - else - reject("Shares are insufficient or incorrect") - }).catch(error => { - clearCredentials(); - location.reload(); - }) - }); - - const writeSharesToIDB = (shares, i = 0, resultIndexes = []) => new Promise(resolve => { - if (i >= shares.length) - return resolve(resultIndexes) - var n = floCrypto.randInt(0, 100000) - compactIDB.addData("credentials", shares[i], n).then(res => { - resultIndexes.push(n) - writeSharesToIDB(shares, i + 1, resultIndexes) - .then(result => resolve(result)) - }).catch(error => { - writeSharesToIDB(shares, i, resultIndexes) - .then(result => resolve(result)) - }) - }); - - const getPrivateKeyCredentials = () => new Promise((resolve, reject) => { - var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`) - if (indexArr) { - readSharesFromIDB(JSON.parse(indexArr)) - .then(result => resolve(result)) - .catch(error => reject(error)) - } else { - var privKey; - keyInput("PRIVATE_KEY").then(result => { - if (!result) - return reject("Empty Private Key") - var floID = floCrypto.getFloID(result) - if (!floID || !floCrypto.validateFloID(floID)) - return reject("Invalid Private Key") - privKey = result; - }).catch(error => { - console.log(error, "Generating Random Keys") - privKey = floCrypto.generateNewID().privKey - }).finally(_ => { - if (!privKey) - return; - var threshold = floCrypto.randInt(10, 20) - var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold) - writeSharesToIDB(shares).then(resultIndexes => { - //store index keys in localStorage - localStorage.setItem(`${DEFAULT.application}#privKey`, JSON.stringify(resultIndexes)) - //also add a dummy privatekey to the IDB - var randomPrivKey = floCrypto.generateNewID().privKey - var randomThreshold = floCrypto.randInt(10, 20) - var randomShares = floCrypto.createShamirsSecretShares(randomPrivKey, randomThreshold, randomThreshold) - writeSharesToIDB(randomShares) - //resolve private Key - resolve(privKey) - }) - }) - } - }); - - const checkIfPinRequired = key => new Promise((resolve, reject) => { - if (key.length == 52) - resolve(key) - else { - keyInput("PIN/Password").then(pwd => { - try { - let privKey = Crypto.AES.decrypt(key, pwd); - resolve(privKey) - } catch (error) { - reject("Access Denied: Incorrect PIN/Password") - } - }).catch(error => reject("Access Denied: PIN/Password required")) - } - }); - - return new Promise((resolve, reject) => { - getPrivateKeyCredentials().then(key => { - checkIfPinRequired(key).then(privKey => { - try { - user_public = floCrypto.getPubKeyHex(privKey); - user_id = floCrypto.getAddress(privKey); - if (startUpOptions.cloud) - floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI - user_priv_wrap = () => checkIfPinRequired(key); - let n = floCrypto.randInt(12, 20); - aes_key = floCrypto.randString(n); - user_priv_raw = Crypto.AES.encrypt(privKey, aes_key); - user_private = user_priv_wrap; - resolve('Login Credentials loaded successful') - } catch (error) { - console.log(error) - reject("Corrupted Private Key") - } - }).catch(error => reject(error)) - }).catch(error => reject(error)) - }) - } - - var startUpLog = (status, log) => status ? console.log(log) : console.error(log); - - const callStartUpFunction = i => new Promise((resolve, reject) => { - startUpFunctions[i]().then(result => { - callStartUpFunction.completed += 1; - startUpLog(true, `${result}\nCompleted ${callStartUpFunction.completed}/${callStartUpFunction.total} Startup functions`) - resolve(true) - }).catch(error => { - callStartUpFunction.failed += 1; - startUpLog(false, `${error}\nFailed ${callStartUpFunction.failed}/${callStartUpFunction.total} Startup functions`) - reject(false) - }) - }); - - var _midFunction; - const midStartUp = () => new Promise((res, rej) => { - if (_midFunction instanceof Function) { - _midFunction() - .then(r => res("Mid startup function completed")) - .catch(e => rej("Mid startup function failed")) - } else - res("No mid startup function") - }); - - const callAndLog = p => new Promise((res, rej) => { - p.then(r => { - startUpLog(true, r) - res(r) - }).catch(e => { - startUpLog(false, e) - rej(e) - }) - }); - - floDapps.launchStartUp = function () { - return new Promise((resolve, reject) => { - initIndexedDB().then(log => { - console.log(log) - callStartUpFunction.total = startUpFunctions.length; - callStartUpFunction.completed = 0; - callStartUpFunction.failed = 0; - let p1 = new Promise((res, rej) => { - Promise.all(startUpFunctions.map((f, i) => callStartUpFunction(i))).then(r => { - callAndLog(midStartUp()) - .then(r => res(true)) - .catch(e => rej(false)) - }) - }); - let p2 = new Promise((res, rej) => { - callAndLog(getCredentials()).then(r => { - callAndLog(initUserDB()).then(r => { - callAndLog(loadUserDB()) - .then(r => res(true)) - .catch(e => rej(false)) - }).catch(e => rej(false)) - }).catch(e => rej(false)) - }) - Promise.all([p1, p2]) - .then(r => resolve('App Startup finished successful')) - .catch(e => reject('App Startup failed')) - }).catch(error => { - startUpLog(false, error); - reject("App database initiation failed") - }) - }) - } - - floDapps.addStartUpFunction = fn => fn instanceof Function && !startUpFunctions.includes(fn) ? startUpFunctions.push(fn) : false; - - floDapps.setMidStartup = fn => fn instanceof Function ? _midFunction = fn : false; - - floDapps.setCustomStartupLogger = fn => fn instanceof Function ? startUpLog = fn : false; - - floDapps.setCustomPrivKeyInput = fn => fn instanceof Function ? keyInput = fn : false; - - floDapps.setAppObjectStores = appObs => initIndexedDB.appObs = appObs; - - floDapps.storeContact = function (floID, name) { - return new Promise((resolve, reject) => { - if (!floCrypto.validateAddr(floID)) - return reject("Invalid floID!") - compactIDB.writeData("contacts", name, floID, user.db_name).then(result => { - user.contacts[floID] = name; - resolve("Contact stored") - }).catch(error => reject(error)) - }); - } - - floDapps.storePubKey = function (floID, pubKey) { - return new Promise((resolve, reject) => { - if (floID in user.pubKeys) - return resolve("pubKey already stored") - if (!floCrypto.validateAddr(floID)) - return reject("Invalid floID!") - if (!floCrypto.verifyPubKey(pubKey, floID)) - return reject("Incorrect pubKey") - compactIDB.writeData("pubKeys", pubKey, floID, user.db_name).then(result => { - user.pubKeys[floID] = pubKey; - resolve("pubKey stored") - }).catch(error => reject(error)) - }); - } - - floDapps.sendMessage = function (floID, message) { - return new Promise((resolve, reject) => { - let options = { - receiverID: floID, - application: DEFAULT.root, - comment: DEFAULT.application - } - if (floID in user.pubKeys) - message = floCrypto.encryptData(JSON.stringify(message), user.pubKeys[floID]) - floCloudAPI.sendApplicationData(message, "Message", options) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - floDapps.requestInbox = function (callback) { - return new Promise((resolve, reject) => { - let lastVC = Object.keys(user.messages).sort().pop() - let options = { - receiverID: user.id, - application: DEFAULT.root, - lowerVectorClock: lastVC + 1 - } - let privKey = raw_user.private; - options.callback = (d, e) => { - for (let v in d) { - try { - if (d[v].message instanceof Object && "secret" in d[v].message) - d[v].message = floCrypto.decryptData(d[v].message, privKey) - } catch (error) { } - compactIDB.writeData("messages", d[v], v, user.db_name) - user.messages[v] = d[v] - } - if (callback instanceof Function) - callback(d, e) - } - floCloudAPI.requestApplicationData("Message", options) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } - - floDapps.manageAppConfig = function (adminPrivKey, addList, rmList, settings) { - return new Promise((resolve, reject) => { - if (!startUpOptions.app_config) - return reject("No configs for this app"); - if (!Array.isArray(addList) || !addList.length) addList = undefined; - if (!Array.isArray(rmList) || !rmList.length) rmList = undefined; - if (!settings || typeof settings !== "object" || !Object.keys(settings).length) settings = undefined; - if (!addList && !rmList && !settings) - return reject("No configuration change") - var floData = { - [DEFAULT.application]: { - addSubAdmin: addList, - removeSubAdmin: rmList, - settings: settings - } - } - var floID = floCrypto.getFloID(adminPrivKey) - if (floID != DEFAULT.adminID) - reject('Access Denied for Admin privilege') - else - floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey) - .then(result => resolve(['Updated App Configuration', result])) - .catch(error => reject(error)) - }) - } - - floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) { - return new Promise((resolve, reject) => { - if (!startUpOptions.app_config) - return reject("No configs for this app"); - if (!Array.isArray(addList) || !addList.length) addList = undefined; - if (!Array.isArray(rmList) || !rmList.length) rmList = undefined; - if (!addList && !rmList) - return reject("No change in list") - var floData = { - [DEFAULT.application]: { - addTrustedID: addList, - removeTrustedID: rmList - } - } - var floID = floCrypto.getFloID(adminPrivKey) - if (floID != DEFAULT.adminID) - reject('Access Denied for Admin privilege') - else - floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey) - .then(result => resolve(['Updated App Configuration', result])) - .catch(error => reject(error)) - }) - } - - const clearCredentials = floDapps.clearCredentials = function () { - return new Promise((resolve, reject) => { - compactIDB.clearData('credentials', DEFAULT.application).then(result => { - localStorage.removeItem(`${DEFAULT.application}#privKey`); - user.clear(); - resolve("privKey credentials deleted!") - }).catch(error => reject(error)) - }) - } - - floDapps.deleteUserData = function (credentials = false) { - return new Promise((resolve, reject) => { - let p = [] - p.push(compactIDB.deleteDB(user.db_name)) - if (credentials) - p.push(clearCredentials()) - Promise.all(p) - .then(result => resolve('User database(local) deleted')) - .catch(error => reject(error)) - }) - } - - floDapps.deleteAppData = function () { - return new Promise((resolve, reject) => { - compactIDB.deleteDB(DEFAULT.application).then(result => { - localStorage.removeItem(`${DEFAULT.application}#privKey`) - user.clear(); - compactIDB.removeData('lastTx', `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root) - .then(result => resolve("App database(local) deleted")) - .catch(error => reject(error)) - }).catch(error => reject(error)) - }) - } - - floDapps.securePrivKey = function (pwd) { - return new Promise(async (resolve, reject) => { - let indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`) - if (!indexArr) - return reject("PrivKey not found"); - indexArr = JSON.parse(indexArr) - let encryptedKey = Crypto.AES.encrypt(await user.private, pwd); - let threshold = indexArr.length; - let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold) - let promises = []; - let overwriteFn = (share, index) => - compactIDB.writeData("credentials", share, index, DEFAULT.application); - for (var i = 0; i < threshold; i++) - promises.push(overwriteFn(shares[i], indexArr[i])); - Promise.all(promises) - .then(results => resolve("Private Key Secured")) - .catch(error => reject(error)) - }) - } - - floDapps.verifyPin = function (pin = null) { - const readSharesFromIDB = function (indexArr) { - return new Promise((resolve, reject) => { - var promises = [] - for (var i = 0; i < indexArr.length; i++) - promises.push(compactIDB.readData('credentials', indexArr[i])) - Promise.all(promises).then(shares => { - var secret = floCrypto.retrieveShamirSecret(shares) - console.info(shares, secret) - if (secret) - resolve(secret) - else - reject("Shares are insufficient or incorrect") - }).catch(error => { - clearCredentials(); - location.reload(); - }) - }) - } - return new Promise((resolve, reject) => { - var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`) - console.info(indexArr) - if (!indexArr) - reject('No login credentials found') - readSharesFromIDB(JSON.parse(indexArr)).then(key => { - if (key.length == 52) { - if (pin === null) - resolve("Private key not secured") - else - reject("Private key not secured") - } else { - if (pin === null) - return reject("PIN/Password required") - try { - let privKey = Crypto.AES.decrypt(key, pin); - resolve("PIN/Password verified") - } catch (error) { - reject("Incorrect PIN/Password") - } - } - }).catch(error => reject(error)) - }) - } - - const getNextGeneralData = floDapps.getNextGeneralData = function (type, vectorClock = null, options = {}) { - var fk = floCloudAPI.util.filterKey(type, options) - vectorClock = vectorClock || getNextGeneralData[fk] || '0'; - var filteredResult = {} - if (floGlobals.generalData[fk]) { - for (let d in floGlobals.generalData[fk]) - if (d > vectorClock) - filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d])) - } else if (options.comment) { - let comment = options.comment; - delete options.comment; - let fk = floCloudAPI.util.filterKey(type, options); - for (let d in floGlobals.generalData[fk]) - if (d > vectorClock && floGlobals.generalData[fk][d].comment == comment) - filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d])) - } - if (options.decrypt) { - let decryptionKey = (options.decrypt === true) ? raw_user.private : options.decrypt; - if (!Array.isArray(decryptionKey)) - decryptionKey = [decryptionKey]; - for (let f in filteredResult) { - let data = filteredResult[f] - try { - if (data.message instanceof Object && "secret" in data.message) { - for (let key of decryptionKey) { - try { - let tmp = floCrypto.decryptData(data.message, key) - data.message = JSON.parse(tmp) - break; - } catch (error) { } - } - } - } catch (error) { } - } - } - getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop(); - return filteredResult; - } - - const syncData = floDapps.syncData = {}; - - syncData.oldDevice = () => new Promise((resolve, reject) => { - let sync = { - contacts: user.contacts, - pubKeys: user.pubKeys, - messages: user.messages - } - let message = Crypto.AES.encrypt(JSON.stringify(sync), raw_user.private) - let options = { - receiverID: user.id, - application: DEFAULT.root - } - floCloudAPI.sendApplicationData(message, "syncData", options) - .then(result => resolve(result)) - .catch(error => reject(error)) - }); - - syncData.newDevice = () => new Promise((resolve, reject) => { - var options = { - receiverID: user.id, - senderID: user.id, - application: DEFAULT.root, - mostRecent: true, - } - floCloudAPI.requestApplicationData("syncData", options).then(response => { - let vc = Object.keys(response).sort().pop() - let sync = JSON.parse(Crypto.AES.decrypt(response[vc].message, raw_user.private)) - let promises = [] - let store = (key, val, obs) => promises.push(compactIDB.writeData(obs, val, key, user.db_name)); - ["contacts", "pubKeys", "messages"].forEach(c => { - for (let i in sync[c]) { - store(i, sync[c][i], c) - user[c][i] = sync[c][i] - } - }) - Promise.all(promises) - .then(results => resolve("Sync data successful")) - .catch(error => reject(error)) - }).catch(error => reject(error)) - }); -})('object' === typeof module ? module.exports : window.floDapps = {}); \ No newline at end of file diff --git a/compactIDB.js b/scripts/compactIDB.js similarity index 100% rename from compactIDB.js rename to scripts/compactIDB.js diff --git a/floBlockchainAPI.js b/scripts/floBlockchainAPI.js similarity index 100% rename from floBlockchainAPI.js rename to scripts/floBlockchainAPI.js diff --git a/floCrypto.js b/scripts/floCrypto.js similarity index 100% rename from floCrypto.js rename to scripts/floCrypto.js diff --git a/floTokenAPI.js b/scripts/floTokenAPI.js similarity index 100% rename from floTokenAPI.js rename to scripts/floTokenAPI.js diff --git a/lib.js b/scripts/lib.js similarity index 100% rename from lib.js rename to scripts/lib.js