Merge pull request #15 from sairajzero/master

This commit is contained in:
Sai Raj 2023-02-08 19:23:28 +05:30 committed by GitHub
commit 837f4b4a6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 4874 additions and 695 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.tmp*

View File

@ -10,7 +10,7 @@ We are offering methods simplifying access to inbuilt browser database IndexedDB
Last but not the least, we are also providing methods for simplying common operations for FLO based Distributed Application Development.
# IMPORTANT
We have two versions of cloud: old cloud version is 2.0.x in floCloudAPI, and new cloud version is 2.1.x in floCloudAPI. Please check that the version in floCloudAPI is 2.1.x whenever you use floCloudAPI as we are deprecating version 2.0.x
We have two versions of cloud: old cloud version is 2.0.x in floCloudAPI, and new cloud version is >2.1.0 in floCloudAPI. Please check that the version in floCloudAPI is >2.1.0 whenever you use floCloudAPI as we are deprecating version 2.0.x
# Background on FLO Distributed Applications
@ -208,6 +208,13 @@ In addition, we have these system variables outside FLO Globals but used globall
1. publickey_or_privateKey - public key or private key hex value
* Returns : floID (string)
#### Calculate Address
floCrypto.getAddress(privateKey, *strict)
`getAddress` returns respective address from given private-key
1. privateKey - private key in WIF format
2. strict - boolean value (optional, default=false) (false: return flo-id if no prefix match is found)
* Returns : address (string)
#### Verify Private Key
floCrypto.verifyPrivKey(privateKey, pubKey_floID, *isfloID)
`verifyPrivKey` verify the private-key for the given public-key or flo-ID
@ -216,12 +223,27 @@ In addition, we have these system variables outside FLO Globals but used globall
3. isfloID - boolean value (true: compare as flo ID, false: compare as public key) (optional, default is true)
* Returns : boolen (true or false)
#### Validate Address
floCrypto.validateAddr(address, *std, *bech)
`validateAddr` check if the given Address (any blockchain) is valid or not
1. address - address to validate
2. std - checks for legacy version (optional, default=true) (true: allow any, array: list of versions, value: one version only, false: allow none)
3. bech - checks for bech version (optional, default=true) (true: allow any, array: list of versions, value: one version only, false: allow none)
* Returns : boolen (true or false)
#### Validate FLO ID
floCrypto.validateAddr(floID)
`validateAddr` check if the given Address is valid or not
floCrypto.validateFloID(floID)
`validateFloID` check if the given floID is valid or not
1. floID - flo ID to validate
* Returns : boolen (true or false)
#### Verify Public Key
floCrypto.verifyPubKey(publicKey, address)
`verifyPubKey` verify the public key for the given address (any blockchain)
1. publicKey - public key
2. address - address to verify
* Returns : boolen (true or false)
#### Data Encryption
floCrypto.encryptData(data, publicKey)
`encryptData` encrypts the given data using public-key

851
btcOperator.js Normal file
View File

@ -0,0 +1,851 @@
(function (EXPORTS) { //btcOperator v1.1.1
/* 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>.*<\/result>/);
if (!r)
reject(resultText);
else {
r = r.pop().replace('<result>', '').replace('</result>', '');
if (r == '1') {
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
resolve(txid);
} else if (r == '0') {
let error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
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);
}
//convert from one blockchain to another blockchain (target version)
btcOperator.convert = {};
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
let keyHex = decodeLegacy(source_wif).hex;
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
return null;
else
return encodeLegacy(keyHex, target_version);
}
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
let rawHex = decodeLegacy(source_addr).hex;
if (!rawHex)
return null;
else
return encodeLegacy(rawHex, target_version);
}
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
let rawHex = decodeLegacy(source_addr).hex;
if (!rawHex)
return null;
else
return encodeBech32(rawHex, target_version, target_hrp);
}
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
let rawHex = decodeBech32(source_addr).hex;
if (!rawHex)
return null;
else
return encodeBech32(rawHex, target_version, target_hrp);
}
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
let rawHex = decodeBech32(source_addr).hex;
if (!rawHex)
return null;
else
return encodeLegacy(rawHex, target_version);
}
function decodeLegacy(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 null;
let version = raw.shift();
return {
version: version,
hex: Crypto.util.bytesToHex(raw)
}
}
function encodeLegacy(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));
}
function decodeBech32(source) {
let decode = coinjs.bech32_decode(source);
if (!decode)
return null;
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)
}
}
function encodeBech32(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);
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
return false;
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;
for (let i = 0; i < tx2.ins.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 = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
break;
case 169: //segwit, multisig-segwit
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
break;
case 118: //legacy
address = 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 = {});

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //floBlockchainAPI v2.3.3
(function (EXPORTS) { //floBlockchainAPI v2.3.3e
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
'use strict';
const floBlockchainAPI = EXPORTS;
@ -6,11 +6,12 @@
const DEFAULT = {
blockchain: floGlobals.blockchain,
apiURL: {
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
FLO: ['https://flosight.duckdns.org/', 'https://flosight.ranchimall.net/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
},
sendAmt: 0.001,
fee: 0.0005,
minChangeAmt: 0.0005,
receiverID: floGlobals.adminID
};
@ -49,7 +50,7 @@
const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]);
var serverList = Array.from(allServerList);
var curPos = floCrypto.randInt(0, serverList - 1);
var curPos = floCrypto.randInt(0, serverList.length - 1);
function fetch_retry(apicall, rm_flosight) {
return new Promise((resolve, reject) => {
@ -102,7 +103,7 @@
});
//Promised function to get data from API
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function(apicall) {
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) {
return new Promise((resolve, reject) => {
//console.log(apicall);
fetch_api(apicall)
@ -112,7 +113,7 @@
}
//Get balance for the given Address
const getBalance = floBlockchainAPI.getBalance = function(addr) {
const getBalance = floBlockchainAPI.getBalance = function (addr) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addr/${addr}/balance`)
.then(balance => resolve(parseFloat(balance)))
@ -121,13 +122,13 @@
}
//Send Tx to blockchain
const sendTx = floBlockchainAPI.sendTx = function(senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
else if (!floCrypto.validateAddr(senderAddr))
else if (!floCrypto.validateFloID(senderAddr))
return reject(`Invalid address : ${senderAddr}`);
else if (!floCrypto.validateAddr(receiverAddr))
else if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid address : ${receiverAddr}`);
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
return reject("Invalid Private key!");
@ -171,7 +172,7 @@
else {
trx.addoutput(receiverAddr, sendAmt);
var change = utxoAmt - sendAmt - fee;
if (change > 0)
if (change > DEFAULT.minChangeAmt)
trx.addoutput(senderAddr, change);
trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1);
@ -187,7 +188,7 @@
}
//Write Data into blockchain
floBlockchainAPI.writeData = function(senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) {
floBlockchainAPI.writeData = function (senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) {
let strict_utxo = options.strict_utxo === false ? false : true,
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
return new Promise((resolve, reject) => {
@ -200,9 +201,9 @@
}
//merge all UTXOs of a given floID into a single UTXO
floBlockchainAPI.mergeUTXOs = function(floID, privKey, floData = '') {
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(floID))
if (!floCrypto.validateFloID(floID))
return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key");
@ -234,7 +235,7 @@
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
* @return {Promise}
*/
floBlockchainAPI.writeDataMultiple = function(senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
return new Promise((resolve, reject) => {
if (!Array.isArray(senderPrivKeys))
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
@ -266,7 +267,7 @@
* @param {string} floData FLO data of the txn
* @return {Promise}
*/
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function(senderPrivKeys, receivers, floData = '') {
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function (senderPrivKeys, receivers, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
@ -326,7 +327,7 @@
}
//Validate the receiver IDs and receive amount
for (let floID in receivers) {
if (!floCrypto.validateAddr(floID))
if (!floCrypto.validateFloID(floID))
invalids.InvalidReceiverIDs.push(floID);
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
invalids.InvalidReceiveAmountFor.push(floID);
@ -371,18 +372,18 @@
})
//Calculate totalSentAmount and check if totalBalance is sufficient
let totalSendAmt = totalFee;
for (floID in receivers)
for (let floID in receivers)
totalSendAmt += receivers[floID];
if (totalBalance < totalSendAmt)
return reject("Insufficient total Balance");
//Get the UTXOs of the senders
let promises = [];
for (floID in senders)
for (let floID in senders)
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
Promise.all(promises).then(results => {
let wifSeq = [];
var trx = bitjs.transaction();
for (floID in senders) {
for (let floID in senders) {
let utxos = results.shift();
let sendAmt;
if (preserveRatio) {
@ -406,7 +407,7 @@
if (change > 0)
trx.addoutput(floID, change);
}
for (floID in receivers)
for (let floID in receivers)
trx.addoutput(floID, receivers[floID]);
trx.addflodata(floData.replace(/\n/g, ' '));
for (let i = 0; i < wifSeq.length; i++)
@ -421,7 +422,7 @@
}
//Broadcast signed Tx in blockchain using API
const broadcastTx = floBlockchainAPI.broadcastTx = function(signedTxHash) {
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
return new Promise((resolve, reject) => {
if (signedTxHash.length < 1)
return reject("Empty Signature");
@ -441,7 +442,7 @@
})
}
floBlockchainAPI.getTx = function(txid) {
floBlockchainAPI.getTx = function (txid) {
return new Promise((resolve, reject) => {
promisedAPI(`api/tx/${txid}`)
.then(response => resolve(response))
@ -450,7 +451,7 @@
}
//Read Txs of Address between from and to
const readTxs = floBlockchainAPI.readTxs = function(addr, from, to) {
const readTxs = floBlockchainAPI.readTxs = function (addr, from, to) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
.then(response => resolve(response))
@ -459,7 +460,7 @@
}
//Read All Txs of Address (newest first)
floBlockchainAPI.readAllTxs = function(addr) {
floBlockchainAPI.readAllTxs = function (addr) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
@ -481,15 +482,15 @@
sender : flo-id(s) of sender
receiver : flo-id(s) of receiver
*/
floBlockchainAPI.readData = function(addr, options = {}) {
floBlockchainAPI.readData = function (addr, options = {}) {
options.limit = options.limit || 0;
options.ignoreOld = options.ignoreOld || 0;
if (typeof options.sender === "string") options.sender = [options.sender];
if (typeof options.receiver === "string") options.receiver = [options.receiver];
if (typeof options.senders === "string") options.senders = [options.senders];
if (typeof options.receivers === "string") options.receivers = [options.receivers];
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
var newItems = response.totalItems - options.ignoreOld;
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems * 2}`).then(response => {
if (options.limit <= 0)
options.limit = response.items.length;
var filteredData = [];
@ -520,10 +521,10 @@
}
if (!flag) continue;
}
if (Array.isArray(options.sender)) {
if (Array.isArray(options.senders)) {
let flag = false;
for (let vin of response.items[i].vin)
if (options.sender.includes(vin.addr)) {
if (options.senders.includes(vin.addr)) {
flag = true;
break;
}
@ -538,10 +539,10 @@
}
if (!flag) continue;
}
if (Array.isArray(options.receiver)) {
if (Array.isArray(options.receivers)) {
let flag = false;
for (let vout of response.items[i].vout)
if (options.receiver.includes(vout.scriptPubKey.addresses[0])) {
if (options.receivers.includes(vout.scriptPubKey.addresses[0])) {
flag = true;
break;
}
@ -555,6 +556,8 @@
d.txid = response.items[i].txid;
d.time = response.items[i].time;
d.blockheight = response.items[i].blockheight;
d.senders = new Set(response.items[i].vin.map(v => v.addr));
d.receivers = new Set(response.items[i].vout.map(v => v.scriptPubKey.addresses[0]));
d.data = response.items[i].floData;
filteredData.push(d);
} else

View File

@ -1,14 +1,59 @@
(function(EXPORTS) { //floCloudAPI v2.3.0
(function (EXPORTS) { //floCloudAPI v2.4.3
/* 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
application: floGlobals.application,
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
@ -18,6 +63,9 @@
},
application: {
get: () => DEFAULT.application
},
user: {
get: () => user
}
});
@ -31,6 +79,9 @@
get: () => generalData,
set: data => generalData = data
},
generalDataset: {
value: (type, options = {}) => generalData[filterKey(type, options)]
},
lastVC: {
get: () => lastVC,
set: vc => lastVC = vc
@ -43,7 +94,7 @@
});
var kBucket;
const K_Bucket = floCloudAPI.K_Bucket = function(masterID, nodeList) {
const K_Bucket = floCloudAPI.K_Bucket = function (masterID, nodeList) {
const decodeID = floID => {
let k = bitjs.Base58.decode(floID);
@ -77,7 +128,7 @@
});
self.isNode = floID => _CO.includes(floID);
self.innerNodes = function(id1, id2) {
self.innerNodes = function (id1, id2) {
if (!_CO.includes(id1) || !_CO.includes(id2))
throw Error('Given nodes are not supernode');
let iNodes = []
@ -88,7 +139,7 @@
}
return iNodes
}
self.outterNodes = function(id1, id2) {
self.outterNodes = function (id1, id2) {
if (!_CO.includes(id1) || !_CO.includes(id2))
throw Error('Given nodes are not supernode');
let oNodes = []
@ -99,7 +150,7 @@
}
return oNodes
}
self.prevNode = function(id, N = 1) {
self.prevNode = function (id, N = 1) {
let n = N || _CO.length;
if (!_CO.includes(id))
throw Error('Given node is not supernode');
@ -113,7 +164,7 @@
}
return (N == 1 ? pNodes[0] : pNodes)
}
self.nextNode = function(id, N = 1) {
self.nextNode = function (id, N = 1) {
let n = N || _CO.length;
if (!_CO.includes(id))
throw Error('Given node is not supernode');
@ -128,7 +179,7 @@
}
return (N == 1 ? nNodes[0] : nNodes)
}
self.closestNode = function(id, N = 1) {
self.closestNode = function (id, N = 1) {
let decodedId = decodeID(id);
let n = N || _CO.length;
let cNodes = _KB.closest(decodedId, n)
@ -175,7 +226,7 @@
if (_inactive.size === kBucket.list.length)
return reject('Cloud offline');
if (!(snID in supernodes))
snID = kBucket.closestNode(snID);
snID = kBucket.closestNode(proxyID(snID));
ws_connect(snID)
.then(node => resolve(node))
.catch(error => {
@ -213,7 +264,7 @@
if (_inactive.size === kBucket.list.length)
return reject('Cloud offline');
if (!(snID in supernodes))
snID = kBucket.closestNode(snID);
snID = kBucket.closestNode(proxyID(snID));
fetch_API(snID, data)
.then(result => resolve(result))
.catch(error => {
@ -242,8 +293,8 @@
fetch_ActiveAPI(floID, data).then(response => {
if (response.ok)
response.json()
.then(result => resolve(result))
.catch(error => reject(error))
.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))
@ -269,6 +320,7 @@
data => {
data = objectifier(data);
let filtered = {},
proxy = proxyID(request.receiverID),
r = request;
for (let v in data) {
let d = data[v];
@ -277,7 +329,7 @@
(r.atVectorClock || !r.upperVectorClock || r.upperVectorClock >= v) &&
(!r.afterTime || r.afterTime < d.log_time) &&
r.application == d.application &&
r.receiverID == d.receiverID &&
(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)))
@ -318,20 +370,64 @@
const util = floCloudAPI.util = {};
const encodeMessage = util.encodeMessage = function(message) {
const encodeMessage = util.encodeMessage = function (message) {
return btoa(unescape(encodeURIComponent(JSON.stringify(message))))
}
const decodeMessage = util.decodeMessage = function(message) {
const decodeMessage = util.decodeMessage = function (message) {
return JSON.parse(decodeURIComponent(escape(atob(message))))
}
const filterKey = util.filterKey = function(type, options) {
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])
@ -392,19 +488,19 @@
}));
}
//set status as online for myFloID
floCloudAPI.setStatus = function(options = {}) {
//set status as online for user_id
floCloudAPI.setStatus = function (options = {}) {
return new Promise((resolve, reject) => {
let callback = options.callback instanceof Function ? options.callback : (d, e) => console.debug(d, e);
let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback;
var request = {
floID: myFloID,
floID: user.id,
application: options.application || DEFAULT.application,
time: Date.now(),
status: true,
pubKey: myPubKey
pubKey: user.public
}
let hashcontent = ["time", "application", "floID"].map(d => request[d]).join("|");
request.sign = floCrypto.signData(hashcontent, myPrivKey);
request.sign = user.sign(hashcontent);
liveRequest(options.refID || DEFAULT.adminID, request, callback)
.then(result => resolve(result))
.catch(error => reject(error))
@ -412,11 +508,11 @@
}
//request status of floID(s) in trackList
floCloudAPI.requestStatus = function(trackList, options = {}) {
floCloudAPI.requestStatus = function (trackList, options = {}) {
return new Promise((resolve, reject) => {
if (!Array.isArray(trackList))
trackList = [trackList];
let callback = options.callback instanceof Function ? options.callback : (d, e) => console.debug(d, e);
let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback;
let request = {
status: false,
application: options.application || DEFAULT.application,
@ -429,12 +525,12 @@
}
//send any message to supernode cloud storage
const sendApplicationData = floCloudAPI.sendApplicationData = function(message, type, options = {}) {
const sendApplicationData = floCloudAPI.sendApplicationData = function (message, type, options = {}) {
return new Promise((resolve, reject) => {
var data = {
senderID: myFloID,
senderID: user.id,
receiverID: options.receiverID || DEFAULT.adminID,
pubKey: myPubKey,
pubKey: user.public,
message: encodeMessage(message),
time: Date.now(),
application: options.application || DEFAULT.application,
@ -443,7 +539,7 @@
}
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
.map(d => data[d]).join("|")
data.sign = floCrypto.signData(hashcontent, myPrivKey);
data.sign = user.sign(hashcontent);
singleRequest(data.receiverID, data)
.then(result => resolve(result))
.catch(error => reject(error))
@ -451,7 +547,7 @@
}
//request any data from supernode cloud
const requestApplicationData = floCloudAPI.requestApplicationData = function(type, options = {}) {
const requestApplicationData = floCloudAPI.requestApplicationData = function (type, options = {}) {
return new Promise((resolve, reject) => {
var request = {
receiverID: options.receiverID || DEFAULT.adminID,
@ -482,19 +578,20 @@
})
}
//(NEEDS UPDATE) delete data from supernode cloud (received only)
/*(NEEDS UPDATE)
//delete data from supernode cloud (received only)
floCloudAPI.deleteApplicationData = function(vectorClocks, options = {}) {
return new Promise((resolve, reject) => {
var delreq = {
requestorID: myFloID,
pubKey: myPubKey,
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 = floCrypto.signData(hashcontent, myPrivKey)
delreq.sign = user.sign(hashcontent)
singleRequest(delreq.requestorID, delreq).then(result => {
let success = [],
failed = [];
@ -507,8 +604,9 @@
}).catch(error => reject(error))
})
}
//(NEEDS UPDATE) edit comment of data in supernode cloud (mutable comments only)
*/
/*(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
@ -523,12 +621,12 @@
}
})
p0.then(d => {
if (d.senderID != myFloID)
if (d.senderID != user.id)
return reject("Invalid requestorID")
else if (!d.comment.startsWith("EDIT:"))
return reject("Data immutable")
let data = {
requestorID: myFloID,
requestorID: user.id,
receiverID: d.receiverID,
time: Date.now(),
application: d.application,
@ -542,29 +640,30 @@
"comment"
]
.map(x => d[x]).join("|")
data.edit.sign = floCrypto.signData(hashcontent, myPrivKey)
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 = {}) {
floCloudAPI.tagApplicationData = function (vectorClock, tag, options = {}) {
return new Promise((resolve, reject) => {
if (!floGlobals.subAdmins.includes(myFloID))
if (!floGlobals.subAdmins.includes(user.id))
return reject("Only subAdmins can tag data")
var request = {
receiverID: options.receiverID || DEFAULT.adminID,
requestorID: myFloID,
pubKey: myPubKey,
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 = floCrypto.signData(hashcontent, myPrivKey);
request.sign = user.sign(hashcontent);
singleRequest(request.receiverID, request)
.then(result => resolve(result))
.catch(error => reject(error))
@ -572,18 +671,18 @@
}
//note data in supernode cloud (receiver only or subAdmin allowed if receiver is adminID)
floCloudAPI.noteApplicationData = function(vectorClock, note, options = {}) {
floCloudAPI.noteApplicationData = function (vectorClock, note, options = {}) {
return new Promise((resolve, reject) => {
var request = {
receiverID: options.receiverID || DEFAULT.adminID,
requestorID: myFloID,
pubKey: myPubKey,
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 = floCrypto.signData(hashcontent, myPrivKey);
request.sign = user.sign(hashcontent);
singleRequest(request.receiverID, request)
.then(result => resolve(result))
.catch(error => reject(error))
@ -591,7 +690,7 @@
}
//send general data
floCloudAPI.sendGeneralData = function(message, type, options = {}) {
floCloudAPI.sendGeneralData = function (message, type, options = {}) {
return new Promise((resolve, reject) => {
if (options.encrypt) {
let encryptionKey = options.encrypt === true ?
@ -605,7 +704,7 @@
}
//request general data
floCloudAPI.requestGeneralData = function(type, options = {}) {
floCloudAPI.requestGeneralData = function (type, options = {}) {
return new Promise((resolve, reject) => {
var fk = filterKey(type, options)
lastVC[fk] = parseInt(lastVC[fk]) || 0;
@ -629,7 +728,7 @@
}
//request an object data from supernode cloud
floCloudAPI.requestObjectData = function(objectName, options = {}) {
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 :
@ -666,7 +765,7 @@
})
}
floCloudAPI.closeRequest = function(requestID) {
floCloudAPI.closeRequest = function (requestID) {
return new Promise((resolve, reject) => {
let conn = _liveRequest[requestID]
if (!conn)
@ -680,7 +779,7 @@
}
//reset or initialize an object and send it to cloud
floCloudAPI.resetObjectData = function(objectName, options = {}) {
floCloudAPI.resetObjectData = function (objectName, options = {}) {
return new Promise((resolve, reject) => {
let message = {
reset: appObjects[objectName]
@ -694,7 +793,7 @@
}
//update the diff and send it to cloud
floCloudAPI.updateObjectData = function(objectName, options = {}) {
floCloudAPI.updateObjectData = function (objectName, options = {}) {
return new Promise((resolve, reject) => {
let message = {
diff: diff.find(lastCommit.get(objectName), appObjects[
@ -713,7 +812,7 @@
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() {
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';
@ -887,23 +986,23 @@
}, {});
};
const mergeRecursive = (obj1, obj2) => {
const mergeRecursive = (obj1, obj2, deleteMode = false) => {
for (var p in obj2) {
try {
if (obj2[p].constructor == Object)
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
// Property in destination object set; update its value.
else if (Ext.isArray(obj2[p])) {
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]);
obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
} else
obj1[p] = obj2[p];
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] = obj2[p];
obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p];
}
}
return obj1;
@ -912,20 +1011,13 @@
const cleanse = (obj) => {
Object.keys(obj).forEach(key => {
var value = obj[key];
if (typeof value === "object" && value !== null) {
// Recurse...
cleanse(value);
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
//if (!Object.keys(value).length)
// delete obj[key];
} else if (value === null)
delete obj[key]; // null, remove it
if (typeof value === "object" && value !== null)
obj[key] = cleanse(value);
else if (typeof value === 'undefined')
delete obj[key]; // undefined, remove it
});
if (obj.constructor.toString().indexOf("Array") != -1) {
obj = obj.filter(function(el) {
return el != null;
});
}
if (Array.isArray(obj))
obj = obj.filter(v => typeof v !== 'undefined');
return obj;
}
@ -941,7 +1033,7 @@
if (Object.keys(diff.updated).length !== 0)
obj = mergeRecursive(obj, diff.updated)
if (Object.keys(diff.deleted).length !== 0) {
obj = mergeRecursive(obj, diff.deleted)
obj = mergeRecursive(obj, diff.deleted, true)
obj = cleanse(obj)
}
if (Object.keys(diff.added).length !== 0)

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //floCrypto v2.3.0a
(function (EXPORTS) { //floCrypto v2.3.3e
/* FLO Crypto Operators */
'use strict';
const floCrypto = EXPORTS;
@ -7,6 +7,7 @@
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
const ascii_alternatives = ` '\n '\n“ "\n” "\n --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
coinjs.compressed = true; //defaulting coinjs compressed to true;
function calculateY(x) {
let exp = exponent1();
@ -77,24 +78,24 @@
}
//generate a random Interger within range
floCrypto.randInt = function(min, max) {
floCrypto.randInt = function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
}
//generate a random String within length (options : alphaNumeric chars only)
floCrypto.randString = function(length, alphaNumeric = true) {
floCrypto.randString = function (length, alphaNumeric = true) {
var result = '';
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
for (var i = 0; i < length; i++)
result += characters.charAt(Math.floor(Math.random() * characters.length));
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
return result;
}
//Encrypt Data using public-key
floCrypto.encryptData = function(data, receiverPublicKeyHex) {
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
var senderECKeyData = getSenderPublicKeyString();
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
@ -106,7 +107,7 @@
}
//Decrypt Data using private-key
floCrypto.decryptData = function(data, privateKeyHex) {
floCrypto.decryptData = function (data, privateKeyHex) {
var receiverECKeyData = {};
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
let privateKey = wifToDecimal(privateKeyHex, true);
@ -119,31 +120,25 @@
}
//Sign data using private-key
floCrypto.signData = function(data, privateKeyHex) {
floCrypto.signData = function (data, privateKeyHex) {
var key = new Bitcoin.ECKey(privateKeyHex);
key.setCompressed(true);
var privateKeyArr = key.getBitcoinPrivateKeyByteArray();
var privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr);
var messageHash = Crypto.SHA256(data);
var messageHashBigInteger = new BigInteger(messageHash);
var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv);
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
var sighex = Crypto.util.bytesToHex(messageSign);
return sighex;
}
//Verify signatue of the data using public-key
floCrypto.verifySign = function(data, signatureHex, publicKeyHex) {
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
var msgHash = Crypto.SHA256(data);
var messageHashBigInteger = new BigInteger(msgHash);
var sigBytes = Crypto.util.hexToBytes(signatureHex);
var signature = Bitcoin.ECDSA.parseSig(sigBytes);
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s, publicKeyPoint);
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
return verify;
}
//Generates a new flo ID and returns private-key, public-key and floID
const generateNewID = floCrypto.generateNewID = function() {
const generateNewID = floCrypto.generateNewID = function () {
var key = new Bitcoin.ECKey(false);
key.setCompressed(true);
return {
@ -153,12 +148,27 @@
}
}
Object.defineProperty(floCrypto, 'newID', {
get: () => generateNewID()
Object.defineProperties(floCrypto, {
newID: {
get: () => generateNewID()
},
tmpID: {
get: () => {
let bytes = Crypto.util.randomBytes(20);
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
}
}
});
//Returns public-key from private-key
floCrypto.getPubKeyHex = function(privateKeyHex) {
floCrypto.getPubKeyHex = function (privateKeyHex) {
if (!privateKeyHex)
return null;
var key = new Bitcoin.ECKey(privateKeyHex);
@ -169,7 +179,7 @@
}
//Returns flo-ID from public-key or private-key
floCrypto.getFloID = function(keyHex) {
floCrypto.getFloID = function (keyHex) {
if (!keyHex)
return null;
try {
@ -182,8 +192,27 @@
}
}
floCrypto.getAddress = function (privateKeyHex, strict = false) {
if (!privateKeyHex)
return;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return null;
key.setCompressed(true);
let pubKey = key.getPubKeyHex(),
version = bitjs.Base58.decode(privateKeyHex)[0];
switch (version) {
case coinjs.priv: //BTC
return coinjs.bech32Address(pubKey).address;
case bitjs.priv: //FLO
return bitjs.pubkey2address(pubKey);
default:
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
}
}
//Verify the private-key for the given public-key or flo-ID
floCrypto.verifyPrivKey = function(privateKeyHex, pubKey_floID, isfloID = true) {
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
if (!privateKeyHex || !pubKey_floID)
return false;
try {
@ -202,20 +231,120 @@
}
}
//Check if the given Address is valid or not
floCrypto.validateFloID = floCrypto.validateAddr = function(inpAddr) {
if (!inpAddr)
//Check if the given flo-id is valid or not
floCrypto.validateFloID = function (floID) {
if (!floID)
return false;
try {
let addr = new Bitcoin.Address(inpAddr);
let addr = new Bitcoin.Address(floID);
return true;
} catch {
return false;
}
}
//Check if the given address (any blockchain) is valid or not
floCrypto.validateAddr = function (address, std = true, bech = true) {
let raw = decodeAddress(address);
if (!raw)
return false;
if (typeof raw.version !== 'undefined') { //legacy or segwit
if (std == false)
return false;
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
return true;
else
return false;
} else if (typeof raw.bech_version !== 'undefined') { //bech32
if (bech === false)
return false;
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
return true;
else
return false;
} else //unknown
return false;
}
//Check the public-key for the address (any blockchain)
floCrypto.verifyPubKey = function (pubKeyHex, address) {
let raw = decodeAddress(address),
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), {
asBytes: true
})));
return raw ? pub_hash === raw.hex : false;
}
//Convert the given address (any blockchain) to equivalent floID
floCrypto.toFloID = function (address, options = null) {
if (!address)
return;
let raw = decodeAddress(address);
if (!raw)
return;
else if (options) {
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
return;
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
return;
}
raw.bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
asBytes: true
}), {
asBytes: true
});
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
}
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
floCrypto.isSameAddr = function (addr1, addr2) {
if (!addr1 || !addr2)
return;
let raw1 = decodeAddress(addr1),
raw2 = decodeAddress(addr2);
if (!raw1 || !raw2)
return false;
else
return raw1.hex === raw2.hex;
}
const decodeAddress = floCrypto.decodeAddr = function (address) {
if (!address)
return;
else if (address.length == 33 || address.length == 34) { //legacy encoding
let decode = bitjs.Base58.decode(address);
let bytes = decode.slice(0, decode.length - 4);
let checksum = decode.slice(decode.length - 4),
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
version: bytes.shift(),
hex: Crypto.util.bytesToHex(bytes),
bytes
}
} else if (address.length == 42) { //bech encoding
let decode = coinjs.bech32_decode(address);
if (decode) {
let bytes = decode.data;
let bech_version = bytes.shift();
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
return {
bech_version,
hrp: decode.hrp,
hex: Crypto.util.bytesToHex(bytes),
bytes
}
} else
return null;
}
}
//Split the str using shamir's Secret and Returns the shares
floCrypto.createShamirsSecretShares = function(str, total_shares, threshold_limit) {
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
try {
if (str.length > 0) {
var strHex = shamirSecretShare.str2hex(str);
@ -229,7 +358,7 @@
}
//Returns the retrived secret by combining the shamirs shares
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function(sharesArray) {
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
try {
if (sharesArray.length > 0) {
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
@ -243,7 +372,7 @@
}
//Verifies the shares and str
floCrypto.verifyShamirsSecret = function(sharesArray, str) {
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
if (!str)
return null;
else if (retrieveShamirSecret(sharesArray) === str)
@ -252,7 +381,7 @@
return false;
}
const validateASCII = floCrypto.validateASCII = function(string, bool = true) {
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
if (typeof string !== "string")
return null;
if (bool) {
@ -270,8 +399,8 @@
if (x < 32 || x > 127)
if (x in invalids)
invalids[string[i]].push(i)
else
invalids[string[i]] = [i];
else
invalids[string[i]] = [i];
}
if (Object.keys(invalids).length)
return invalids;
@ -280,7 +409,7 @@
}
}
floCrypto.convertToASCII = function(string, mode = 'soft-remove') {
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
let chars = validateASCII(string, false);
if (chars === true)
return string;
@ -291,9 +420,9 @@
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
mode = mode.toLowerCase();
if (mode === "hard-unicode")
convertor = (c) => `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`;
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "soft-unicode")
convertor = (c) => refAlt[c] || `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`;
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "hard-remove")
convertor = c => "";
else if (mode === "soft-remove")
@ -305,7 +434,7 @@
return result;
}
floCrypto.revertUnicode = function(string) {
floCrypto.revertUnicode = function (string) {
return string.replace(/\\u[\dA-F]{4}/gi,
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
}

View File

@ -1,8 +1,168 @@
(function(EXPORTS) { //floDapps v2.2.1
(function (EXPORTS) { //floDapps v2.3.3
/* General functions for FLO Dapps*/
//'use strict';
'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, settings
Object.defineProperties(floGlobals, {
subAdmins: {
get: () => subAdmins
},
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 = {
@ -21,6 +181,7 @@
credentials: {},
//for Dapps
subAdmins: {},
trustedIDs: {},
settings: {},
appObjects: {},
generalData: {},
@ -28,41 +189,41 @@
}
//add other given objectStores
initIndexedDB.appObs = initIndexedDB.appObs || {}
for (o in initIndexedDB.appObs)
for (let o in initIndexedDB.appObs)
if (!(o in obs_a))
obs_a[o] = initIndexedDB.appObs[o]
Promise.all([
compactIDB.initDB(floGlobals.application, obs_a),
compactIDB.initDB("floDapps", obs_g)
compactIDB.initDB(DEFAULT.application, obs_a),
compactIDB.initDB(DEFAULT.root, obs_g)
]).then(result => {
compactIDB.setDefaultDB(floGlobals.application)
compactIDB.setDefaultDB(DEFAULT.application)
resolve("IndexedDB App Storage Initated Successfully")
}).catch(error => reject(error));
})
}
function initUserDB(floID) {
function initUserDB() {
return new Promise((resolve, reject) => {
var obs = {
contacts: {},
pubKeys: {},
messages: {}
}
compactIDB.initDB(`floDapps#${floID}`, obs).then(result => {
compactIDB.initDB(user.db_name, obs).then(result => {
resolve("UserDB Initated Successfully")
}).catch(error => reject('Init userDB failed'));
})
}
function loadUserDB(floID) {
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], `floDapps#${floID}`)
promises[i] = compactIDB.readAllData(loadData[i], user.db_name)
Promise.all(promises).then(results => {
for (var i = 0; i < loadData.length; i++)
floGlobals[loadData[i]] = results[i]
user[loadData[i]] = results[i]
resolve("Loaded Data from userDB")
}).catch(error => reject('Load userDB failed'))
})
@ -72,7 +233,7 @@
startUpFunctions.push(function readSupernodeListFromAPI() {
return new Promise((resolve, reject) => {
compactIDB.readData("lastTx", floCloudAPI.SNStorageID, "floDapps").then(lastTx => {
compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => {
floBlockchainAPI.readData(floCloudAPI.SNStorageID, {
ignoreOld: lastTx,
sentOnly: true,
@ -80,14 +241,20 @@
}).then(result => {
for (var i = result.data.length - 1; i >= 0; i--) {
var content = JSON.parse(result.data[i]).SuperNodeStorage;
for (sn in content.removeNodes)
compactIDB.removeData("supernodes", sn, "floDapps");
for (sn in content.newNodes)
compactIDB.writeData("supernodes", content.newNodes[sn], sn, "floDapps");
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.totalTxs, floCloudAPI.SNStorageID, "floDapps");
compactIDB.readAllData("supernodes", "floDapps").then(result => {
floCloudAPI.init(result)
compactIDB.writeData("lastTx", result.totalTxs, 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))
})
@ -98,14 +265,14 @@
startUpFunctions.push(function readAppConfigFromAPI() {
return new Promise((resolve, reject) => {
compactIDB.readData("lastTx", `${floGlobals.application}|${floGlobals.adminID}`, "floDapps").then(lastTx => {
floBlockchainAPI.readData(floGlobals.adminID, {
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
floBlockchainAPI.readData(DEFAULT.adminID, {
ignoreOld: lastTx,
sentOnly: true,
pattern: floGlobals.application
pattern: DEFAULT.application
}).then(result => {
for (var i = result.data.length - 1; i >= 0; i--) {
var content = JSON.parse(result.data[i])[floGlobals.application];
var content = JSON.parse(result.data[i])[DEFAULT.application];
if (!content || typeof content !== "object")
continue;
if (Array.isArray(content.removeSubAdmin))
@ -114,15 +281,21 @@
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.totalTxs, `${floGlobals.application}|${floGlobals.adminID}`, "floDapps");
compactIDB.writeData("lastTx", result.totalTxs, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root);
compactIDB.readAllData("subAdmins").then(result => {
floGlobals.subAdmins = Object.keys(result);
subAdmins = Object.keys(result);
compactIDB.readAllData("settings").then(result => {
floGlobals.settings = result;
settings = result;
resolve("Read app configuration from blockchain");
})
})
@ -186,7 +359,7 @@
});
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
if (indexArr) {
readSharesFromIDB(JSON.parse(indexArr))
.then(result => resolve(result))
@ -197,7 +370,7 @@
if (!result)
return reject("Empty Private Key")
var floID = floCrypto.getFloID(result)
if (!floID || !floCrypto.validateAddr(floID))
if (!floID || !floCrypto.validateFloID(floID))
return reject("Invalid Private Key")
privKey = result;
}).catch(error => {
@ -210,7 +383,7 @@
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
writeSharesToIDB(shares).then(resultIndexes => {
//store index keys in localStorage
localStorage.setItem(`${floGlobals.application}#privKey`, JSON.stringify(resultIndexes))
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)
@ -242,9 +415,14 @@
getPrivateKeyCredentials().then(key => {
checkIfPinRequired(key).then(privKey => {
try {
myPrivKey = privKey
myPubKey = floCrypto.getPubKeyHex(myPrivKey)
myFloID = floCrypto.getFloID(myPubKey)
user_public = floCrypto.getPubKeyHex(privKey);
user_id = floCrypto.getAddress(privKey);
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)
@ -289,7 +467,7 @@
})
});
floDapps.launchStartUp = function() {
floDapps.launchStartUp = function () {
return new Promise((resolve, reject) => {
initIndexedDB().then(log => {
console.log(log)
@ -305,8 +483,8 @@
});
let p2 = new Promise((res, rej) => {
callAndLog(getCredentials()).then(r => {
callAndLog(initUserDB(myFloID)).then(r => {
callAndLog(loadUserDB(myFloID))
callAndLog(initUserDB()).then(r => {
callAndLog(loadUserDB())
.then(r => res(true))
.catch(e => rej(false))
}).catch(e => rej(false))
@ -315,7 +493,10 @@
Promise.all([p1, p2])
.then(r => resolve('App Startup finished successful'))
.catch(e => reject('App Startup failed'))
}).catch(error => reject("App database initiation failed"))
}).catch(error => {
startUpLog(false, error);
reject("App database initiation failed")
})
})
}
@ -329,63 +510,64 @@
floDapps.setAppObjectStores = appObs => initIndexedDB.appObs = appObs;
floDapps.storeContact = function(floID, name) {
floDapps.storeContact = function (floID, name) {
return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(floID))
return reject("Invalid floID!")
compactIDB.writeData("contacts", name, floID, `floDapps#${myFloID}`).then(result => {
floGlobals.contacts[floID] = name;
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) {
floDapps.storePubKey = function (floID, pubKey) {
return new Promise((resolve, reject) => {
if (floID in floGlobals.pubKeys)
if (floID in user.pubKeys)
return resolve("pubKey already stored")
if (!floCrypto.validateAddr(floID))
return reject("Invalid floID!")
if (floCrypto.getFloID(pubKey) != floID)
if (!floCrypto.verifyPubKey(pubKey, floID))
return reject("Incorrect pubKey")
compactIDB.writeData("pubKeys", pubKey, floID, `floDapps#${myFloID}`).then(result => {
floGlobals.pubKeys[floID] = 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) {
floDapps.sendMessage = function (floID, message) {
return new Promise((resolve, reject) => {
let options = {
receiverID: floID,
application: "floDapps",
comment: floGlobals.application
application: DEFAULT.root,
comment: DEFAULT.application
}
if (floID in floGlobals.pubKeys)
message = floCrypto.encryptData(JSON.stringify(message), floGlobals.pubKeys[floID])
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) {
floDapps.requestInbox = function (callback) {
return new Promise((resolve, reject) => {
let lastVC = Object.keys(floGlobals.messages).sort().pop()
let lastVC = Object.keys(user.messages).sort().pop()
let options = {
receiverID: myFloID,
application: "floDapps",
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, myPrivKey)
} catch (error) {}
compactIDB.writeData("messages", d[v], v, `floDapps#${myFloID}`)
floGlobals.messages[v] = d[v]
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)
@ -396,7 +578,7 @@
})
}
floDapps.manageAppConfig = function(adminPrivKey, addList, rmList, settings) {
floDapps.manageAppConfig = function (adminPrivKey, addList, rmList, settings) {
return new Promise((resolve, reject) => {
if (!Array.isArray(addList) || !addList.length) addList = undefined;
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
@ -404,36 +586,58 @@
if (!addList && !rmList && !settings)
return reject("No configuration change")
var floData = {
[floGlobals.application]: {
[DEFAULT.application]: {
addSubAdmin: addList,
removeSubAdmin: rmList,
settings: settings
}
}
var floID = floCrypto.getFloID(adminPrivKey)
if (floID != floGlobals.adminID)
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))
.then(result => resolve(['Updated App Configuration', result]))
.catch(error => reject(error))
})
}
const clearCredentials = floDapps.clearCredentials = function() {
floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) {
return new Promise((resolve, reject) => {
compactIDB.clearData('credentials', floGlobals.application).then(result => {
localStorage.removeItem(`${floGlobals.application}#privKey`)
myPrivKey = myPubKey = myFloID = undefined;
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) {
floDapps.deleteUserData = function (credentials = false) {
return new Promise((resolve, reject) => {
let p = []
p.push(compactIDB.deleteDB(`floDapps#${myFloID}`))
p.push(compactIDB.deleteDB(user.db_name))
if (credentials)
p.push(clearCredentials())
Promise.all(p)
@ -442,30 +646,30 @@
})
}
floDapps.deleteAppData = function() {
floDapps.deleteAppData = function () {
return new Promise((resolve, reject) => {
compactIDB.deleteDB(floGlobals.application).then(result => {
localStorage.removeItem(`${floGlobals.application}#privKey`)
myPrivKey = myPubKey = myFloID = undefined;
compactIDB.removeData('lastTx', `${floGlobals.application}|${floGlobals.adminID}`, 'floDapps')
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((resolve, reject) => {
let indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
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(myPrivKey, pwd);
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, floGlobals.application);
compactIDB.writeData("credentials", share, index, DEFAULT.application);
for (var i = 0; i < threshold; i++)
promises.push(overwriteFn(shares[i], indexArr[i]));
Promise.all(promises)
@ -474,8 +678,8 @@
})
}
floDapps.verifyPin = function(pin = null) {
const readSharesFromIDB = function(indexArr) {
floDapps.verifyPin = function (pin = null) {
const readSharesFromIDB = function (indexArr) {
return new Promise((resolve, reject) => {
var promises = []
for (var i = 0; i < indexArr.length; i++)
@ -494,7 +698,7 @@
})
}
return new Promise((resolve, reject) => {
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
console.info(indexArr)
if (!indexArr)
reject('No login credentials found')
@ -518,7 +722,7 @@
})
}
const getNextGeneralData = floDapps.getNextGeneralData = function(type, vectorClock = null, options = {}) {
const getNextGeneralData = floDapps.getNextGeneralData = function (type, vectorClock = null, options = {}) {
var fk = floCloudAPI.util.filterKey(type, options)
vectorClock = vectorClock || getNextGeneralData[fk] || '0';
var filteredResult = {}
@ -535,7 +739,7 @@
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
}
if (options.decrypt) {
let decryptionKey = (options.decrypt === true) ? myPrivKey : options.decrypt;
let decryptionKey = (options.decrypt === true) ? raw_user.private : options.decrypt;
if (!Array.isArray(decryptionKey))
decryptionKey = [decryptionKey];
for (let f in filteredResult) {
@ -547,10 +751,10 @@
let tmp = floCrypto.decryptData(data.message, key)
data.message = JSON.parse(tmp)
break;
} catch (error) {}
} catch (error) { }
}
}
} catch (error) {}
} catch (error) { }
}
}
getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop();
@ -561,14 +765,14 @@
syncData.oldDevice = () => new Promise((resolve, reject) => {
let sync = {
contacts: floGlobals.contacts,
pubKeys: floGlobals.pubKeys,
messages: floGlobals.messages
contacts: user.contacts,
pubKeys: user.pubKeys,
messages: user.messages
}
let message = Crypto.AES.encrypt(JSON.stringify(sync), myPrivKey)
let message = Crypto.AES.encrypt(JSON.stringify(sync), raw_user.private)
let options = {
receiverID: myFloID,
application: "floDapps"
receiverID: user.id,
application: DEFAULT.root
}
floCloudAPI.sendApplicationData(message, "syncData", options)
.then(result => resolve(result))
@ -577,20 +781,20 @@
syncData.newDevice = () => new Promise((resolve, reject) => {
var options = {
receiverID: myFloID,
senderID: myFloID,
application: "floDapps",
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, myPrivKey))
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, `floDapps#${floID}`));
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)
floGlobals[c][i] = sync[c][i]
user[c][i] = sync[c][i]
}
})
Promise.all(promises)

View File

@ -1,11 +1,11 @@
(function(EXPORTS) { //floTokenAPI v1.0.3b
(function (EXPORTS) { //floTokenAPI v1.0.3c
/* Token Operator to send/receive tokens via blockchain using API calls*/
'use strict';
const tokenAPI = EXPORTS;
const DEFAULT = {
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
currency: "rupee"
currency: floGlobals.currency || "rupee"
}
Object.defineProperties(tokenAPI, {
@ -27,9 +27,9 @@
}
});
const fetch_api = tokenAPI.fetch = function(apicall) {
const fetch_api = tokenAPI.fetch = function (apicall) {
return new Promise((resolve, reject) => {
console.log(DEFAULT.apiURL + apicall);
console.debug(DEFAULT.apiURL + apicall);
fetch(DEFAULT.apiURL + apicall).then(response => {
if (response.ok)
response.json().then(data => resolve(data));
@ -39,7 +39,7 @@
})
}
const getBalance = tokenAPI.getBalance = function(floID, token = DEFAULT.currency) {
const getBalance = tokenAPI.getBalance = function (floID, token = DEFAULT.currency) {
return new Promise((resolve, reject) => {
fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
.then(result => resolve(result.balance || 0))
@ -47,7 +47,7 @@
})
}
tokenAPI.getTx = function(txID) {
tokenAPI.getTx = function (txID) {
return new Promise((resolve, reject) => {
fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
if (res.result === "error")
@ -62,7 +62,7 @@
})
}
tokenAPI.sendToken = function(privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) {
tokenAPI.sendToken = function (privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) {
return new Promise((resolve, reject) => {
let senderID = floCrypto.getFloID(privKey);
if (typeof amount !== "number" || isNaN(amount) || amount <= 0)
@ -77,7 +77,7 @@
});
}
tokenAPI.getAllTxs = function(floID, token = DEFAULT.currency) {
tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
return new Promise((resolve, reject) => {
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
.then(result => resolve(result))
@ -87,7 +87,7 @@
const util = tokenAPI.util = {};
util.parseTxData = function(txData) {
util.parseTxData = function (txData) {
let parsedData = {};
for (let p in txData.parsedFloData)
parsedData[p] = txData.parsedFloData[p];

View File

@ -13,6 +13,7 @@
</script>
<script src="lib.js"></script>
<script src="floCrypto.js"></script>
<script src="btcOperator.js"></script>
<script src="floBlockchainAPI.js"></script>
<script src="floTokenAPI.js"></script>
<script src="compactIDB.js"></script>
@ -24,7 +25,6 @@
//floDapps.addStartUpFunction('Sample', Promised Function)
//floDapps.setAppObjectStores({sampleObs1:{}, sampleObs2:{options{autoIncrement:true, keyPath:'SampleKey'}, Indexes:{sampleIndex:{}}}})
//floDapps.setCustomPrivKeyInput( () => { FUNCTION BODY *must resolve private key* } )
floDapps.launchStartUp().then(result => {
console.log(result)
alert(`Welcome FLO_ID: ${myFloID}`)

3763
lib.js

File diff suppressed because it is too large Load Diff