Merge pull request #15 from sairajzero/master
This commit is contained in:
commit
837f4b4a6f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.tmp*
|
||||
28
README.md
28
README.md
@ -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
851
btcOperator.js
Normal 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 = {});
|
||||
@ -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
|
||||
|
||||
240
floCloudAPI.js
240
floCloudAPI.js
@ -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)
|
||||
|
||||
203
floCrypto.js
203
floCrypto.js
@ -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)));
|
||||
}
|
||||
|
||||
398
floDapps.js
398
floDapps.js
@ -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)
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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}`)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user