From 471f29184846956373eb99894800dbb73823dcc5 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Wed, 26 Jul 2023 03:45:35 +0530 Subject: [PATCH 1/4] floCloudAPI v2.4.4: edit comment feature - senders can now edit the comment of data they sent (by re-signing the new data) --- floCloudAPI.js | 64 +++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/floCloudAPI.js b/floCloudAPI.js index c2d2c3f..2cf5ed4 100644 --- a/floCloudAPI.js +++ b/floCloudAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCloudAPI v2.4.3a +(function (EXPORTS) { //floCloudAPI v2.4.4 /* FLO Cloud operations to send/request application data*/ 'use strict'; const floCloudAPI = EXPORTS; @@ -609,49 +609,39 @@ }) } */ - /*(NEEDS UPDATE) - //edit comment of data in supernode cloud (mutable comments only) - floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, options = {}) { + //edit comment of data in supernode cloud (sender only) + floCloudAPI.editApplicationData = function (vectorClock, comment_edit, 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 = { + //request the data from cloud for resigning + let req_options = Object.assign({}, options); + req_options.atVectorClock = vectorClock; + requestApplicationData(undefined, req_options).then(result => { + if (!result.length) + return reject("Data not found"); + let data = result[0]; + if (data.senderID !== user.id) + return reject("Only sender can edit comment"); + data.comment = comment_edit; + let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"] + .map(d => data[d]).join("|"); + let re_sign = user.sign(hashcontent); + var request = { + receiverID: options.receiverID || DEFAULT.adminID, requestorID: user.id, - receiverID: d.receiverID, + pubKey: user.public, time: Date.now(), - application: d.application, - edit: { - vectorClock: vectorClock, - comment: newComment - } + vectorClock: vectorClock, + edit: comment_edit, + re_sign: re_sign } - 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")) + let request_hash = ["time", "vectorClock", "edit", "re_sign"].map(d => request[d]).join("|"); + request.sign = user.sign(request_hash); + singleRequest(request.receiverID, request) + .then(result => resolve(result)) .catch(error => reject(error)) - }) + }).catch(error => reject(error)) }) } - */ //tag data in supernode cloud (subAdmin access only) floCloudAPI.tagApplicationData = function (vectorClock, tag, options = {}) { From 5be35c28ce87b88d73ecc29428a2c304c1d33c78 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 29 Jul 2023 05:01:14 +0530 Subject: [PATCH 2/4] floDapps v2.4.1 - support for multiple cloud networks --- floDapps.js | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/floDapps.js b/floDapps.js index 4d525a2..480e4e4 100644 --- a/floDapps.js +++ b/floDapps.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floDapps v2.4.0 +(function (EXPORTS) { //floDapps v2.4.1 /* General functions for FLO Dapps*/ 'use strict'; const floDapps = EXPORTS; @@ -172,12 +172,7 @@ //general lastTx: {}, //supernode (cloud list) - supernodes: { - indexes: { - uri: null, - pubKey: null - } - } + supernodes: {} } var obs_a = { //login credentials @@ -257,7 +252,8 @@ return new Promise((resolve, reject) => { if (!startUpOptions.cloud) return resolve("No cloud for this app"); - compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => { + const CLOUD_KEY = "floCloudAPI#" + floCloudAPI.SNStorageID; + compactIDB.readData("lastTx", CLOUD_KEY, 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; @@ -265,25 +261,27 @@ 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)) - }) + compactIDB.readData("supernodes", CLOUD_KEY, DEFAULT.root).then(nodes => { + nodes = nodes || {}; + 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) + delete nodes[sn]; + for (let sn in content.newNodes) + nodes[sn] = content.newNodes[sn]; + for (let sn in content.updateNodes) + if (sn in nodes) //check if node is listed + nodes[sn].uri = content.updateNodes[sn]; + } + Promise.all([ + compactIDB.writeData("lastTx", result.lastItem, CLOUD_KEY, DEFAULT.root), + compactIDB.writeData("supernodes", nodes, CLOUD_KEY, DEFAULT.root) + ]).then(_ => { + floCloudAPI.init(nodes) + .then(result => resolve("Loaded Supernode list\n" + result)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) }) }).catch(error => reject(error)) }) From 098a62047cb2a5b9c872910c2457d2a6e5f68eda Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 29 Jul 2023 05:41:06 +0530 Subject: [PATCH 3/4] floCloudAPI v2.4.5 : file support - uploadFile: upload a file to the cloud - downloadFile: download a file from cloud - upload/download file supports encryption of file. use options.encrypt in uploadFile and options.decrypt in downloadFile --- floCloudAPI.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/floCloudAPI.js b/floCloudAPI.js index 2cf5ed4..db50d11 100644 --- a/floCloudAPI.js +++ b/floCloudAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCloudAPI v2.4.4 +(function (EXPORTS) { //floCloudAPI v2.4.5 /* FLO Cloud operations to send/request application data*/ 'use strict'; const floCloudAPI = EXPORTS; @@ -801,6 +801,67 @@ }) } + //upload file + floCloudAPI.uploadFile = function (fileBlob, type, options = {}) { + return new Promise((resolve, reject) => { + if (!(fileBlob instanceof File) && !(fileBlob instanceof Blob)) + return reject("file must be instance of File/Blob"); + fileBlob.arrayBuffer().then(arraybuf => { + let file_data = { type: fileBlob.type, name: fileBlob.name }; + file_data.content = Crypto.util.bytesToBase64(new Uint8Array(arraybuf)); + if (options.encrypt) { + let encryptionKey = options.encrypt === true ? + floGlobals.settings.encryptionKey : options.encrypt + file_data = floCrypto.encryptData(JSON.stringify(file_data), encryptionKey) + } + sendApplicationData(file_data, type, options) + .then(({ vectorClock, receiverID, type, application }) => resolve({ vectorClock, receiverID, type, application })) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + + //download file + floCloudAPI.downloadFile = function (vectorClock, options = {}) { + return new Promise((resolve, reject) => { + options.atVectorClock = vectorClock; + requestApplicationData(options.type, options).then(result => { + if (!result.length) + return reject("File not found"); + result = result[0]; + try { + let file_data = decodeMessage(result.message); + //file is encrypted: decryption required + if (file_data instanceof Object && "secret" in file_data) { + if (!options.decrypt) + return reject("Data is encrypted"); + let decryptionKey = (options.decrypt === true) ? Crypto.AES.decrypt(user_private, aes_key) : options.decrypt; + if (!Array.isArray(decryptionKey)) + decryptionKey = [decryptionKey]; + let flag = false; + for (let key of decryptionKey) { + try { + let tmp = floCrypto.decryptData(file_data, key); + file_data = JSON.parse(tmp); + flag = true; + break; + } catch (error) { } + } + if (!flag) + return reject("Unable to decrypt file: Invalid private key"); + } + //reconstruct the file + let arraybuf = new Uint8Array(Crypto.util.base64ToBytes(file_data.content)) + result.file = new File([arraybuf], file_data.name, { type: file_data.type }); + resolve(result) + } catch (error) { + console.error(error); + reject("Data is not a file"); + } + }).catch(error => reject(error)) + }) + } + /* Functions: findDiff(original, updatedObj) returns an object with the added, deleted and updated differences From 2c656d40ba0f10edcd53f20b928596d80df09b84 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 31 Jul 2023 02:18:43 +0530 Subject: [PATCH 4/4] btcOperator v1.1.4 - CONSTANTS, get_fee_rate and some other useful fns are added to util - getUTXOs can now be called directly using btcOperator.getUTXOs --- btcOperator.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/btcOperator.js b/btcOperator.js index 2abb80f..d452bf4 100644 --- a/btcOperator.js +++ b/btcOperator.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //btcOperator v1.1.3b +(function (EXPORTS) { //btcOperator v1.1.4 /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; @@ -44,6 +44,7 @@ }).catch(error => reject(error)) }) } + util.get_fee_rate = get_fee_rate; const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => { let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction'; @@ -282,6 +283,14 @@ BECH32_MULTISIG_OUTPUT_SIZE = 34, SEGWIT_OUTPUT_SIZE = 23; + btcOperator.CONSTANTS = { + DUST_AMT, MIN_FEE_UPDATE, SATOSHI_IN_BTC, + BASE_TX_SIZE, BASE_INPUT_SIZE, LEGACY_INPUT_SIZE, BECH32_INPUT_SIZE, + BECH32_MULTISIG_INPUT_SIZE, SEGWIT_INPUT_SIZE, MULTISIG_INPUT_SIZE_ES, + BASE_OUTPUT_SIZE, LEGACY_OUTPUT_SIZE, BECH32_OUTPUT_SIZE, + BECH32_MULTISIG_OUTPUT_SIZE, SEGWIT_OUTPUT_SIZE + }; + function _redeemScript(addr, key) { let decode = coinjs.addressDecode(addr); switch (decode.type) { @@ -317,6 +326,7 @@ return null; } } + util.sizePerInput = _sizePerInput; function _sizePerOutput(addr) { switch (coinjs.addressDecode(addr).type) { @@ -332,6 +342,7 @@ return null; } } + util.sizePerOutput = _sizePerOutput; function validateTxParameters(parameters) { let invalids = []; @@ -442,6 +453,20 @@ }) } + const getUTXOs = btcOperator.getUTXOs = addr => new Promise((resolve, reject) => { + fetch_api(`unspent?active=${addr}`).then(result => { + let utxos = result.unspent_outputs; + resolve(utxos.map(u => ({ + txid: u.tx_hash_big_endian, + vout: u.tx_output_n, + n: u.tx_output_n, + value: u.value, + value_hex: u.value_hex, + script: u.script + }))) + }).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)); @@ -462,8 +487,7 @@ 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; + getUTXOs(addr).then(utxos => { //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 @@ -480,7 +504,7 @@ 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 + tx.addinput(utxos[i].txid, utxos[i].vout, 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);