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.
|
Last but not the least, we are also providing methods for simplying common operations for FLO based Distributed Application Development.
|
||||||
|
|
||||||
# IMPORTANT
|
# 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
|
# 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
|
1. publickey_or_privateKey - public key or private key hex value
|
||||||
* Returns : floID (string)
|
* 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
|
#### Verify Private Key
|
||||||
floCrypto.verifyPrivKey(privateKey, pubKey_floID, *isfloID)
|
floCrypto.verifyPrivKey(privateKey, pubKey_floID, *isfloID)
|
||||||
`verifyPrivKey` verify the private-key for the given public-key or flo-ID
|
`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)
|
3. isfloID - boolean value (true: compare as flo ID, false: compare as public key) (optional, default is true)
|
||||||
* Returns : boolen (true or false)
|
* 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
|
#### Validate FLO ID
|
||||||
floCrypto.validateAddr(floID)
|
floCrypto.validateFloID(floID)
|
||||||
`validateAddr` check if the given Address is valid or not
|
`validateFloID` check if the given floID is valid or not
|
||||||
1. floID - flo ID to validate
|
1. floID - flo ID to validate
|
||||||
* Returns : boolen (true or false)
|
* 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
|
#### Data Encryption
|
||||||
floCrypto.encryptData(data, publicKey)
|
floCrypto.encryptData(data, publicKey)
|
||||||
`encryptData` encrypts the given data using public-key
|
`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*/
|
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const floBlockchainAPI = EXPORTS;
|
const floBlockchainAPI = EXPORTS;
|
||||||
@ -6,11 +6,12 @@
|
|||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
blockchain: floGlobals.blockchain,
|
blockchain: floGlobals.blockchain,
|
||||||
apiURL: {
|
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/']
|
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
|
||||||
},
|
},
|
||||||
sendAmt: 0.001,
|
sendAmt: 0.001,
|
||||||
fee: 0.0005,
|
fee: 0.0005,
|
||||||
|
minChangeAmt: 0.0005,
|
||||||
receiverID: floGlobals.adminID
|
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]);
|
const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]);
|
||||||
|
|
||||||
var serverList = Array.from(allServerList);
|
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) {
|
function fetch_retry(apicall, rm_flosight) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -102,7 +103,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Promised function to get data from API
|
//Promised function to get data from API
|
||||||
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function(apicall) {
|
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
//console.log(apicall);
|
//console.log(apicall);
|
||||||
fetch_api(apicall)
|
fetch_api(apicall)
|
||||||
@ -112,7 +113,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Get balance for the given Address
|
//Get balance for the given Address
|
||||||
const getBalance = floBlockchainAPI.getBalance = function(addr) {
|
const getBalance = floBlockchainAPI.getBalance = function (addr) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addr/${addr}/balance`)
|
promisedAPI(`api/addr/${addr}/balance`)
|
||||||
.then(balance => resolve(parseFloat(balance)))
|
.then(balance => resolve(parseFloat(balance)))
|
||||||
@ -121,13 +122,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Send Tx to blockchain
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.validateASCII(floData))
|
if (!floCrypto.validateASCII(floData))
|
||||||
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
else if (!floCrypto.validateAddr(senderAddr))
|
else if (!floCrypto.validateFloID(senderAddr))
|
||||||
return reject(`Invalid address : ${senderAddr}`);
|
return reject(`Invalid address : ${senderAddr}`);
|
||||||
else if (!floCrypto.validateAddr(receiverAddr))
|
else if (!floCrypto.validateFloID(receiverAddr))
|
||||||
return reject(`Invalid address : ${receiverAddr}`);
|
return reject(`Invalid address : ${receiverAddr}`);
|
||||||
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
|
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
|
||||||
return reject("Invalid Private key!");
|
return reject("Invalid Private key!");
|
||||||
@ -171,7 +172,7 @@
|
|||||||
else {
|
else {
|
||||||
trx.addoutput(receiverAddr, sendAmt);
|
trx.addoutput(receiverAddr, sendAmt);
|
||||||
var change = utxoAmt - sendAmt - fee;
|
var change = utxoAmt - sendAmt - fee;
|
||||||
if (change > 0)
|
if (change > DEFAULT.minChangeAmt)
|
||||||
trx.addoutput(senderAddr, change);
|
trx.addoutput(senderAddr, change);
|
||||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
var signedTxHash = trx.sign(privKey, 1);
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
@ -187,7 +188,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Write Data into blockchain
|
//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,
|
let strict_utxo = options.strict_utxo === false ? false : true,
|
||||||
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
|
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -200,9 +201,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//merge all UTXOs of a given floID into a single UTXO
|
//merge all UTXOs of a given floID into a single UTXO
|
||||||
floBlockchainAPI.mergeUTXOs = function(floID, privKey, floData = '') {
|
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.validateAddr(floID))
|
if (!floCrypto.validateFloID(floID))
|
||||||
return reject(`Invalid floID`);
|
return reject(`Invalid floID`);
|
||||||
if (!floCrypto.verifyPrivKey(privKey, floID))
|
if (!floCrypto.verifyPrivKey(privKey, floID))
|
||||||
return reject("Invalid Private Key");
|
return reject("Invalid Private Key");
|
||||||
@ -234,7 +235,7 @@
|
|||||||
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
|
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
floBlockchainAPI.writeDataMultiple = function(senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
|
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!Array.isArray(senderPrivKeys))
|
if (!Array.isArray(senderPrivKeys))
|
||||||
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
|
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
|
||||||
@ -266,7 +267,7 @@
|
|||||||
* @param {string} floData FLO data of the txn
|
* @param {string} floData FLO data of the txn
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function(senderPrivKeys, receivers, floData = '') {
|
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function (senderPrivKeys, receivers, floData = '') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.validateASCII(floData))
|
if (!floCrypto.validateASCII(floData))
|
||||||
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
@ -326,7 +327,7 @@
|
|||||||
}
|
}
|
||||||
//Validate the receiver IDs and receive amount
|
//Validate the receiver IDs and receive amount
|
||||||
for (let floID in receivers) {
|
for (let floID in receivers) {
|
||||||
if (!floCrypto.validateAddr(floID))
|
if (!floCrypto.validateFloID(floID))
|
||||||
invalids.InvalidReceiverIDs.push(floID);
|
invalids.InvalidReceiverIDs.push(floID);
|
||||||
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
|
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
|
||||||
invalids.InvalidReceiveAmountFor.push(floID);
|
invalids.InvalidReceiveAmountFor.push(floID);
|
||||||
@ -371,18 +372,18 @@
|
|||||||
})
|
})
|
||||||
//Calculate totalSentAmount and check if totalBalance is sufficient
|
//Calculate totalSentAmount and check if totalBalance is sufficient
|
||||||
let totalSendAmt = totalFee;
|
let totalSendAmt = totalFee;
|
||||||
for (floID in receivers)
|
for (let floID in receivers)
|
||||||
totalSendAmt += receivers[floID];
|
totalSendAmt += receivers[floID];
|
||||||
if (totalBalance < totalSendAmt)
|
if (totalBalance < totalSendAmt)
|
||||||
return reject("Insufficient total Balance");
|
return reject("Insufficient total Balance");
|
||||||
//Get the UTXOs of the senders
|
//Get the UTXOs of the senders
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (floID in senders)
|
for (let floID in senders)
|
||||||
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
|
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
|
||||||
Promise.all(promises).then(results => {
|
Promise.all(promises).then(results => {
|
||||||
let wifSeq = [];
|
let wifSeq = [];
|
||||||
var trx = bitjs.transaction();
|
var trx = bitjs.transaction();
|
||||||
for (floID in senders) {
|
for (let floID in senders) {
|
||||||
let utxos = results.shift();
|
let utxos = results.shift();
|
||||||
let sendAmt;
|
let sendAmt;
|
||||||
if (preserveRatio) {
|
if (preserveRatio) {
|
||||||
@ -406,7 +407,7 @@
|
|||||||
if (change > 0)
|
if (change > 0)
|
||||||
trx.addoutput(floID, change);
|
trx.addoutput(floID, change);
|
||||||
}
|
}
|
||||||
for (floID in receivers)
|
for (let floID in receivers)
|
||||||
trx.addoutput(floID, receivers[floID]);
|
trx.addoutput(floID, receivers[floID]);
|
||||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
for (let i = 0; i < wifSeq.length; i++)
|
for (let i = 0; i < wifSeq.length; i++)
|
||||||
@ -421,7 +422,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Broadcast signed Tx in blockchain using API
|
//Broadcast signed Tx in blockchain using API
|
||||||
const broadcastTx = floBlockchainAPI.broadcastTx = function(signedTxHash) {
|
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (signedTxHash.length < 1)
|
if (signedTxHash.length < 1)
|
||||||
return reject("Empty Signature");
|
return reject("Empty Signature");
|
||||||
@ -441,7 +442,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floBlockchainAPI.getTx = function(txid) {
|
floBlockchainAPI.getTx = function (txid) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/tx/${txid}`)
|
promisedAPI(`api/tx/${txid}`)
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
@ -450,7 +451,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Read Txs of Address between from and to
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
|
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
@ -459,7 +460,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Read All Txs of Address (newest first)
|
//Read All Txs of Address (newest first)
|
||||||
floBlockchainAPI.readAllTxs = function(addr) {
|
floBlockchainAPI.readAllTxs = function (addr) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
|
||||||
@ -481,15 +482,15 @@
|
|||||||
sender : flo-id(s) of sender
|
sender : flo-id(s) of sender
|
||||||
receiver : flo-id(s) of receiver
|
receiver : flo-id(s) of receiver
|
||||||
*/
|
*/
|
||||||
floBlockchainAPI.readData = function(addr, options = {}) {
|
floBlockchainAPI.readData = function (addr, options = {}) {
|
||||||
options.limit = options.limit || 0;
|
options.limit = options.limit || 0;
|
||||||
options.ignoreOld = options.ignoreOld || 0;
|
options.ignoreOld = options.ignoreOld || 0;
|
||||||
if (typeof options.sender === "string") options.sender = [options.sender];
|
if (typeof options.senders === "string") options.senders = [options.senders];
|
||||||
if (typeof options.receiver === "string") options.receiver = [options.receiver];
|
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
var newItems = response.totalItems - options.ignoreOld;
|
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)
|
if (options.limit <= 0)
|
||||||
options.limit = response.items.length;
|
options.limit = response.items.length;
|
||||||
var filteredData = [];
|
var filteredData = [];
|
||||||
@ -520,10 +521,10 @@
|
|||||||
}
|
}
|
||||||
if (!flag) continue;
|
if (!flag) continue;
|
||||||
}
|
}
|
||||||
if (Array.isArray(options.sender)) {
|
if (Array.isArray(options.senders)) {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
for (let vin of response.items[i].vin)
|
for (let vin of response.items[i].vin)
|
||||||
if (options.sender.includes(vin.addr)) {
|
if (options.senders.includes(vin.addr)) {
|
||||||
flag = true;
|
flag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -538,10 +539,10 @@
|
|||||||
}
|
}
|
||||||
if (!flag) continue;
|
if (!flag) continue;
|
||||||
}
|
}
|
||||||
if (Array.isArray(options.receiver)) {
|
if (Array.isArray(options.receivers)) {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
for (let vout of response.items[i].vout)
|
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;
|
flag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -555,6 +556,8 @@
|
|||||||
d.txid = response.items[i].txid;
|
d.txid = response.items[i].txid;
|
||||||
d.time = response.items[i].time;
|
d.time = response.items[i].time;
|
||||||
d.blockheight = response.items[i].blockheight;
|
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;
|
d.data = response.items[i].floData;
|
||||||
filteredData.push(d);
|
filteredData.push(d);
|
||||||
} else
|
} 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*/
|
/* FLO Cloud operations to send/request application data*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const floCloudAPI = EXPORTS;
|
const floCloudAPI = EXPORTS;
|
||||||
|
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
|
blockchainPrefix: 0x23, //Prefix version for FLO blockchain
|
||||||
SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
|
SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
|
||||||
adminID: floGlobals.adminID,
|
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, {
|
Object.defineProperties(floCloudAPI, {
|
||||||
SNStorageID: {
|
SNStorageID: {
|
||||||
get: () => DEFAULT.SNStorageID
|
get: () => DEFAULT.SNStorageID
|
||||||
@ -18,6 +63,9 @@
|
|||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
get: () => DEFAULT.application
|
get: () => DEFAULT.application
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
get: () => user
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -31,6 +79,9 @@
|
|||||||
get: () => generalData,
|
get: () => generalData,
|
||||||
set: data => generalData = data
|
set: data => generalData = data
|
||||||
},
|
},
|
||||||
|
generalDataset: {
|
||||||
|
value: (type, options = {}) => generalData[filterKey(type, options)]
|
||||||
|
},
|
||||||
lastVC: {
|
lastVC: {
|
||||||
get: () => lastVC,
|
get: () => lastVC,
|
||||||
set: vc => lastVC = vc
|
set: vc => lastVC = vc
|
||||||
@ -43,7 +94,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
var kBucket;
|
var kBucket;
|
||||||
const K_Bucket = floCloudAPI.K_Bucket = function(masterID, nodeList) {
|
const K_Bucket = floCloudAPI.K_Bucket = function (masterID, nodeList) {
|
||||||
|
|
||||||
const decodeID = floID => {
|
const decodeID = floID => {
|
||||||
let k = bitjs.Base58.decode(floID);
|
let k = bitjs.Base58.decode(floID);
|
||||||
@ -77,7 +128,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.isNode = floID => _CO.includes(floID);
|
self.isNode = floID => _CO.includes(floID);
|
||||||
self.innerNodes = function(id1, id2) {
|
self.innerNodes = function (id1, id2) {
|
||||||
if (!_CO.includes(id1) || !_CO.includes(id2))
|
if (!_CO.includes(id1) || !_CO.includes(id2))
|
||||||
throw Error('Given nodes are not supernode');
|
throw Error('Given nodes are not supernode');
|
||||||
let iNodes = []
|
let iNodes = []
|
||||||
@ -88,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
return iNodes
|
return iNodes
|
||||||
}
|
}
|
||||||
self.outterNodes = function(id1, id2) {
|
self.outterNodes = function (id1, id2) {
|
||||||
if (!_CO.includes(id1) || !_CO.includes(id2))
|
if (!_CO.includes(id1) || !_CO.includes(id2))
|
||||||
throw Error('Given nodes are not supernode');
|
throw Error('Given nodes are not supernode');
|
||||||
let oNodes = []
|
let oNodes = []
|
||||||
@ -99,7 +150,7 @@
|
|||||||
}
|
}
|
||||||
return oNodes
|
return oNodes
|
||||||
}
|
}
|
||||||
self.prevNode = function(id, N = 1) {
|
self.prevNode = function (id, N = 1) {
|
||||||
let n = N || _CO.length;
|
let n = N || _CO.length;
|
||||||
if (!_CO.includes(id))
|
if (!_CO.includes(id))
|
||||||
throw Error('Given node is not supernode');
|
throw Error('Given node is not supernode');
|
||||||
@ -113,7 +164,7 @@
|
|||||||
}
|
}
|
||||||
return (N == 1 ? pNodes[0] : pNodes)
|
return (N == 1 ? pNodes[0] : pNodes)
|
||||||
}
|
}
|
||||||
self.nextNode = function(id, N = 1) {
|
self.nextNode = function (id, N = 1) {
|
||||||
let n = N || _CO.length;
|
let n = N || _CO.length;
|
||||||
if (!_CO.includes(id))
|
if (!_CO.includes(id))
|
||||||
throw Error('Given node is not supernode');
|
throw Error('Given node is not supernode');
|
||||||
@ -128,7 +179,7 @@
|
|||||||
}
|
}
|
||||||
return (N == 1 ? nNodes[0] : nNodes)
|
return (N == 1 ? nNodes[0] : nNodes)
|
||||||
}
|
}
|
||||||
self.closestNode = function(id, N = 1) {
|
self.closestNode = function (id, N = 1) {
|
||||||
let decodedId = decodeID(id);
|
let decodedId = decodeID(id);
|
||||||
let n = N || _CO.length;
|
let n = N || _CO.length;
|
||||||
let cNodes = _KB.closest(decodedId, n)
|
let cNodes = _KB.closest(decodedId, n)
|
||||||
@ -175,7 +226,7 @@
|
|||||||
if (_inactive.size === kBucket.list.length)
|
if (_inactive.size === kBucket.list.length)
|
||||||
return reject('Cloud offline');
|
return reject('Cloud offline');
|
||||||
if (!(snID in supernodes))
|
if (!(snID in supernodes))
|
||||||
snID = kBucket.closestNode(snID);
|
snID = kBucket.closestNode(proxyID(snID));
|
||||||
ws_connect(snID)
|
ws_connect(snID)
|
||||||
.then(node => resolve(node))
|
.then(node => resolve(node))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -213,7 +264,7 @@
|
|||||||
if (_inactive.size === kBucket.list.length)
|
if (_inactive.size === kBucket.list.length)
|
||||||
return reject('Cloud offline');
|
return reject('Cloud offline');
|
||||||
if (!(snID in supernodes))
|
if (!(snID in supernodes))
|
||||||
snID = kBucket.closestNode(snID);
|
snID = kBucket.closestNode(proxyID(snID));
|
||||||
fetch_API(snID, data)
|
fetch_API(snID, data)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -242,8 +293,8 @@
|
|||||||
fetch_ActiveAPI(floID, data).then(response => {
|
fetch_ActiveAPI(floID, data).then(response => {
|
||||||
if (response.ok)
|
if (response.ok)
|
||||||
response.json()
|
response.json()
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
else response.text()
|
else response.text()
|
||||||
.then(result => reject(response.status + ": " + result)) //Error Message from Node
|
.then(result => reject(response.status + ": " + result)) //Error Message from Node
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
@ -269,6 +320,7 @@
|
|||||||
data => {
|
data => {
|
||||||
data = objectifier(data);
|
data = objectifier(data);
|
||||||
let filtered = {},
|
let filtered = {},
|
||||||
|
proxy = proxyID(request.receiverID),
|
||||||
r = request;
|
r = request;
|
||||||
for (let v in data) {
|
for (let v in data) {
|
||||||
let d = data[v];
|
let d = data[v];
|
||||||
@ -277,7 +329,7 @@
|
|||||||
(r.atVectorClock || !r.upperVectorClock || r.upperVectorClock >= v) &&
|
(r.atVectorClock || !r.upperVectorClock || r.upperVectorClock >= v) &&
|
||||||
(!r.afterTime || r.afterTime < d.log_time) &&
|
(!r.afterTime || r.afterTime < d.log_time) &&
|
||||||
r.application == d.application &&
|
r.application == d.application &&
|
||||||
r.receiverID == d.receiverID &&
|
(proxy == d.receiverID || proxy == d.proxyID) &&
|
||||||
(!r.comment || r.comment == d.comment) &&
|
(!r.comment || r.comment == d.comment) &&
|
||||||
(!r.type || r.type == d.type) &&
|
(!r.type || r.type == d.type) &&
|
||||||
(!r.senderID || r.senderID.includes(d.senderID)))
|
(!r.senderID || r.senderID.includes(d.senderID)))
|
||||||
@ -318,20 +370,64 @@
|
|||||||
|
|
||||||
const util = floCloudAPI.util = {};
|
const util = floCloudAPI.util = {};
|
||||||
|
|
||||||
const encodeMessage = util.encodeMessage = function(message) {
|
const encodeMessage = util.encodeMessage = function (message) {
|
||||||
return btoa(unescape(encodeURIComponent(JSON.stringify(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))))
|
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 : '') +
|
return type + (options.comment ? ':' + options.comment : '') +
|
||||||
'|' + (options.group || options.receiverID || DEFAULT.adminID) +
|
'|' + (options.group || options.receiverID || DEFAULT.adminID) +
|
||||||
'|' + (options.application || DEFAULT.application);
|
'|' + (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 = {};
|
const lastCommit = {};
|
||||||
Object.defineProperty(lastCommit, 'get', {
|
Object.defineProperty(lastCommit, 'get', {
|
||||||
value: objName => JSON.parse(lastCommit[objName])
|
value: objName => JSON.parse(lastCommit[objName])
|
||||||
@ -392,19 +488,19 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
//set status as online for myFloID
|
//set status as online for user_id
|
||||||
floCloudAPI.setStatus = function(options = {}) {
|
floCloudAPI.setStatus = function (options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
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 = {
|
var request = {
|
||||||
floID: myFloID,
|
floID: user.id,
|
||||||
application: options.application || DEFAULT.application,
|
application: options.application || DEFAULT.application,
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
status: true,
|
status: true,
|
||||||
pubKey: myPubKey
|
pubKey: user.public
|
||||||
}
|
}
|
||||||
let hashcontent = ["time", "application", "floID"].map(d => request[d]).join("|");
|
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)
|
liveRequest(options.refID || DEFAULT.adminID, request, callback)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
@ -412,11 +508,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//request status of floID(s) in trackList
|
//request status of floID(s) in trackList
|
||||||
floCloudAPI.requestStatus = function(trackList, options = {}) {
|
floCloudAPI.requestStatus = function (trackList, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!Array.isArray(trackList))
|
if (!Array.isArray(trackList))
|
||||||
trackList = [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 = {
|
let request = {
|
||||||
status: false,
|
status: false,
|
||||||
application: options.application || DEFAULT.application,
|
application: options.application || DEFAULT.application,
|
||||||
@ -429,12 +525,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//send any message to supernode cloud storage
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
var data = {
|
var data = {
|
||||||
senderID: myFloID,
|
senderID: user.id,
|
||||||
receiverID: options.receiverID || DEFAULT.adminID,
|
receiverID: options.receiverID || DEFAULT.adminID,
|
||||||
pubKey: myPubKey,
|
pubKey: user.public,
|
||||||
message: encodeMessage(message),
|
message: encodeMessage(message),
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
application: options.application || DEFAULT.application,
|
application: options.application || DEFAULT.application,
|
||||||
@ -443,7 +539,7 @@
|
|||||||
}
|
}
|
||||||
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
|
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
|
||||||
.map(d => data[d]).join("|")
|
.map(d => data[d]).join("|")
|
||||||
data.sign = floCrypto.signData(hashcontent, myPrivKey);
|
data.sign = user.sign(hashcontent);
|
||||||
singleRequest(data.receiverID, data)
|
singleRequest(data.receiverID, data)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
@ -451,7 +547,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//request any data from supernode cloud
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
var request = {
|
var request = {
|
||||||
receiverID: options.receiverID || DEFAULT.adminID,
|
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 = {}) {
|
floCloudAPI.deleteApplicationData = function(vectorClocks, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var delreq = {
|
var delreq = {
|
||||||
requestorID: myFloID,
|
requestorID: user.id,
|
||||||
pubKey: myPubKey,
|
pubKey: user.public,
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
delete: (Array.isArray(vectorClocks) ? vectorClocks : [vectorClocks]),
|
delete: (Array.isArray(vectorClocks) ? vectorClocks : [vectorClocks]),
|
||||||
application: options.application || DEFAULT.application
|
application: options.application || DEFAULT.application
|
||||||
}
|
}
|
||||||
let hashcontent = ["time", "application", "delete"]
|
let hashcontent = ["time", "application", "delete"]
|
||||||
.map(d => delreq[d]).join("|")
|
.map(d => delreq[d]).join("|")
|
||||||
delreq.sign = floCrypto.signData(hashcontent, myPrivKey)
|
delreq.sign = user.sign(hashcontent)
|
||||||
singleRequest(delreq.requestorID, delreq).then(result => {
|
singleRequest(delreq.requestorID, delreq).then(result => {
|
||||||
let success = [],
|
let success = [],
|
||||||
failed = [];
|
failed = [];
|
||||||
@ -507,8 +604,9 @@
|
|||||||
}).catch(error => reject(error))
|
}).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 = {}) {
|
floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let p0
|
let p0
|
||||||
@ -523,12 +621,12 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
p0.then(d => {
|
p0.then(d => {
|
||||||
if (d.senderID != myFloID)
|
if (d.senderID != user.id)
|
||||||
return reject("Invalid requestorID")
|
return reject("Invalid requestorID")
|
||||||
else if (!d.comment.startsWith("EDIT:"))
|
else if (!d.comment.startsWith("EDIT:"))
|
||||||
return reject("Data immutable")
|
return reject("Data immutable")
|
||||||
let data = {
|
let data = {
|
||||||
requestorID: myFloID,
|
requestorID: user.id,
|
||||||
receiverID: d.receiverID,
|
receiverID: d.receiverID,
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
application: d.application,
|
application: d.application,
|
||||||
@ -542,29 +640,30 @@
|
|||||||
"comment"
|
"comment"
|
||||||
]
|
]
|
||||||
.map(x => d[x]).join("|")
|
.map(x => d[x]).join("|")
|
||||||
data.edit.sign = floCrypto.signData(hashcontent, myPrivKey)
|
data.edit.sign = user.sign(hashcontent)
|
||||||
singleRequest(data.receiverID, data)
|
singleRequest(data.receiverID, data)
|
||||||
.then(result => resolve("Data comment updated"))
|
.then(result => resolve("Data comment updated"))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
//tag data in supernode cloud (subAdmin access only)
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floGlobals.subAdmins.includes(myFloID))
|
if (!floGlobals.subAdmins.includes(user.id))
|
||||||
return reject("Only subAdmins can tag data")
|
return reject("Only subAdmins can tag data")
|
||||||
var request = {
|
var request = {
|
||||||
receiverID: options.receiverID || DEFAULT.adminID,
|
receiverID: options.receiverID || DEFAULT.adminID,
|
||||||
requestorID: myFloID,
|
requestorID: user.id,
|
||||||
pubKey: myPubKey,
|
pubKey: user.public,
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
vectorClock: vectorClock,
|
vectorClock: vectorClock,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
}
|
}
|
||||||
let hashcontent = ["time", "vectorClock", 'tag'].map(d => request[d]).join("|");
|
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)
|
singleRequest(request.receiverID, request)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
@ -572,18 +671,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//note data in supernode cloud (receiver only or subAdmin allowed if receiver is adminID)
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
var request = {
|
var request = {
|
||||||
receiverID: options.receiverID || DEFAULT.adminID,
|
receiverID: options.receiverID || DEFAULT.adminID,
|
||||||
requestorID: myFloID,
|
requestorID: user.id,
|
||||||
pubKey: myPubKey,
|
pubKey: user.public,
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
vectorClock: vectorClock,
|
vectorClock: vectorClock,
|
||||||
note: note,
|
note: note,
|
||||||
}
|
}
|
||||||
let hashcontent = ["time", "vectorClock", 'note'].map(d => request[d]).join("|");
|
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)
|
singleRequest(request.receiverID, request)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
@ -591,7 +690,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//send general data
|
//send general data
|
||||||
floCloudAPI.sendGeneralData = function(message, type, options = {}) {
|
floCloudAPI.sendGeneralData = function (message, type, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (options.encrypt) {
|
if (options.encrypt) {
|
||||||
let encryptionKey = options.encrypt === true ?
|
let encryptionKey = options.encrypt === true ?
|
||||||
@ -605,7 +704,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//request general data
|
//request general data
|
||||||
floCloudAPI.requestGeneralData = function(type, options = {}) {
|
floCloudAPI.requestGeneralData = function (type, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var fk = filterKey(type, options)
|
var fk = filterKey(type, options)
|
||||||
lastVC[fk] = parseInt(lastVC[fk]) || 0;
|
lastVC[fk] = parseInt(lastVC[fk]) || 0;
|
||||||
@ -629,7 +728,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//request an object data from supernode cloud
|
//request an object data from supernode cloud
|
||||||
floCloudAPI.requestObjectData = function(objectName, options = {}) {
|
floCloudAPI.requestObjectData = function (objectName, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
options.lowerVectorClock = options.lowerVectorClock || lastVC[objectName] + 1;
|
options.lowerVectorClock = options.lowerVectorClock || lastVC[objectName] + 1;
|
||||||
options.senderID = [false, null].includes(options.senderID) ? null :
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let conn = _liveRequest[requestID]
|
let conn = _liveRequest[requestID]
|
||||||
if (!conn)
|
if (!conn)
|
||||||
@ -680,7 +779,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//reset or initialize an object and send it to cloud
|
//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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let message = {
|
let message = {
|
||||||
reset: appObjects[objectName]
|
reset: appObjects[objectName]
|
||||||
@ -694,7 +793,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//update the diff and send it to cloud
|
//update the diff and send it to cloud
|
||||||
floCloudAPI.updateObjectData = function(objectName, options = {}) {
|
floCloudAPI.updateObjectData = function (objectName, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let message = {
|
let message = {
|
||||||
diff: diff.find(lastCommit.get(objectName), appObjects[
|
diff: diff.find(lastCommit.get(objectName), appObjects[
|
||||||
@ -713,7 +812,7 @@
|
|||||||
findDiff(original, updatedObj) returns an object with the added, deleted and updated differences
|
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)
|
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 isDate = d => d instanceof Date;
|
||||||
const isEmpty = o => Object.keys(o).length === 0;
|
const isEmpty = o => Object.keys(o).length === 0;
|
||||||
const isObject = o => o != null && typeof o === 'object';
|
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) {
|
for (var p in obj2) {
|
||||||
try {
|
try {
|
||||||
if (obj2[p].constructor == Object)
|
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.
|
// Property in destination object set; update its value.
|
||||||
else if (Ext.isArray(obj2[p])) {
|
else if (Array.isArray(obj2[p])) {
|
||||||
// obj1[p] = [];
|
// obj1[p] = [];
|
||||||
if (obj2[p].length < 1)
|
if (obj2[p].length < 1)
|
||||||
obj1[p] = obj2[p];
|
obj1[p] = obj2[p];
|
||||||
else
|
else
|
||||||
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
|
obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
|
||||||
} else
|
} else
|
||||||
obj1[p] = obj2[p];
|
obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Property in destination object not set; create it and set its value.
|
// 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;
|
return obj1;
|
||||||
@ -912,20 +1011,13 @@
|
|||||||
const cleanse = (obj) => {
|
const cleanse = (obj) => {
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach(key => {
|
||||||
var value = obj[key];
|
var value = obj[key];
|
||||||
if (typeof value === "object" && value !== null) {
|
if (typeof value === "object" && value !== null)
|
||||||
// Recurse...
|
obj[key] = cleanse(value);
|
||||||
cleanse(value);
|
else if (typeof value === 'undefined')
|
||||||
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
|
delete obj[key]; // undefined, remove it
|
||||||
//if (!Object.keys(value).length)
|
|
||||||
// delete obj[key];
|
|
||||||
} else if (value === null)
|
|
||||||
delete obj[key]; // null, remove it
|
|
||||||
});
|
});
|
||||||
if (obj.constructor.toString().indexOf("Array") != -1) {
|
if (Array.isArray(obj))
|
||||||
obj = obj.filter(function(el) {
|
obj = obj.filter(v => typeof v !== 'undefined');
|
||||||
return el != null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -941,7 +1033,7 @@
|
|||||||
if (Object.keys(diff.updated).length !== 0)
|
if (Object.keys(diff.updated).length !== 0)
|
||||||
obj = mergeRecursive(obj, diff.updated)
|
obj = mergeRecursive(obj, diff.updated)
|
||||||
if (Object.keys(diff.deleted).length !== 0) {
|
if (Object.keys(diff.deleted).length !== 0) {
|
||||||
obj = mergeRecursive(obj, diff.deleted)
|
obj = mergeRecursive(obj, diff.deleted, true)
|
||||||
obj = cleanse(obj)
|
obj = cleanse(obj)
|
||||||
}
|
}
|
||||||
if (Object.keys(diff.added).length !== 0)
|
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 */
|
/* FLO Crypto Operators */
|
||||||
'use strict';
|
'use strict';
|
||||||
const floCrypto = EXPORTS;
|
const floCrypto = EXPORTS;
|
||||||
@ -7,6 +7,7 @@
|
|||||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
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 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"));
|
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||||
|
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||||
|
|
||||||
function calculateY(x) {
|
function calculateY(x) {
|
||||||
let exp = exponent1();
|
let exp = exponent1();
|
||||||
@ -77,24 +78,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//generate a random Interger within range
|
//generate a random Interger within range
|
||||||
floCrypto.randInt = function(min, max) {
|
floCrypto.randInt = function (min, max) {
|
||||||
min = Math.ceil(min);
|
min = Math.ceil(min);
|
||||||
max = Math.floor(max);
|
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)
|
//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 result = '';
|
||||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||||
for (var i = 0; i < length; i++)
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Encrypt Data using public-key
|
//Encrypt Data using public-key
|
||||||
floCrypto.encryptData = function(data, receiverPublicKeyHex) {
|
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||||
var senderECKeyData = getSenderPublicKeyString();
|
var senderECKeyData = getSenderPublicKeyString();
|
||||||
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||||
@ -106,7 +107,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Decrypt Data using private-key
|
//Decrypt Data using private-key
|
||||||
floCrypto.decryptData = function(data, privateKeyHex) {
|
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||||
var receiverECKeyData = {};
|
var receiverECKeyData = {};
|
||||||
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||||
@ -119,31 +120,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Sign data using private-key
|
//Sign data using private-key
|
||||||
floCrypto.signData = function(data, privateKeyHex) {
|
floCrypto.signData = function (data, privateKeyHex) {
|
||||||
var key = new Bitcoin.ECKey(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 messageHash = Crypto.SHA256(data);
|
||||||
var messageHashBigInteger = new BigInteger(messageHash);
|
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||||
var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv);
|
|
||||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||||
return sighex;
|
return sighex;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verify signatue of the data using public-key
|
//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 msgHash = Crypto.SHA256(data);
|
||||||
var messageHashBigInteger = new BigInteger(msgHash);
|
|
||||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||||
var signature = Bitcoin.ECDSA.parseSig(sigBytes);
|
|
||||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
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;
|
return verify;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generates a new flo ID and returns private-key, public-key and floID
|
//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);
|
var key = new Bitcoin.ECKey(false);
|
||||||
key.setCompressed(true);
|
key.setCompressed(true);
|
||||||
return {
|
return {
|
||||||
@ -153,12 +148,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(floCrypto, 'newID', {
|
Object.defineProperties(floCrypto, {
|
||||||
get: () => generateNewID()
|
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
|
//Returns public-key from private-key
|
||||||
floCrypto.getPubKeyHex = function(privateKeyHex) {
|
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||||
if (!privateKeyHex)
|
if (!privateKeyHex)
|
||||||
return null;
|
return null;
|
||||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||||
@ -169,7 +179,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Returns flo-ID from public-key or private-key
|
//Returns flo-ID from public-key or private-key
|
||||||
floCrypto.getFloID = function(keyHex) {
|
floCrypto.getFloID = function (keyHex) {
|
||||||
if (!keyHex)
|
if (!keyHex)
|
||||||
return null;
|
return null;
|
||||||
try {
|
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
|
//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)
|
if (!privateKeyHex || !pubKey_floID)
|
||||||
return false;
|
return false;
|
||||||
try {
|
try {
|
||||||
@ -202,20 +231,120 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if the given Address is valid or not
|
//Check if the given flo-id is valid or not
|
||||||
floCrypto.validateFloID = floCrypto.validateAddr = function(inpAddr) {
|
floCrypto.validateFloID = function (floID) {
|
||||||
if (!inpAddr)
|
if (!floID)
|
||||||
return false;
|
return false;
|
||||||
try {
|
try {
|
||||||
let addr = new Bitcoin.Address(inpAddr);
|
let addr = new Bitcoin.Address(floID);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
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
|
//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 {
|
try {
|
||||||
if (str.length > 0) {
|
if (str.length > 0) {
|
||||||
var strHex = shamirSecretShare.str2hex(str);
|
var strHex = shamirSecretShare.str2hex(str);
|
||||||
@ -229,7 +358,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Returns the retrived secret by combining the shamirs shares
|
//Returns the retrived secret by combining the shamirs shares
|
||||||
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function(sharesArray) {
|
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
|
||||||
try {
|
try {
|
||||||
if (sharesArray.length > 0) {
|
if (sharesArray.length > 0) {
|
||||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||||
@ -243,7 +372,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Verifies the shares and str
|
//Verifies the shares and str
|
||||||
floCrypto.verifyShamirsSecret = function(sharesArray, str) {
|
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||||
if (!str)
|
if (!str)
|
||||||
return null;
|
return null;
|
||||||
else if (retrieveShamirSecret(sharesArray) === str)
|
else if (retrieveShamirSecret(sharesArray) === str)
|
||||||
@ -252,7 +381,7 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateASCII = floCrypto.validateASCII = function(string, bool = true) {
|
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
|
||||||
if (typeof string !== "string")
|
if (typeof string !== "string")
|
||||||
return null;
|
return null;
|
||||||
if (bool) {
|
if (bool) {
|
||||||
@ -270,8 +399,8 @@
|
|||||||
if (x < 32 || x > 127)
|
if (x < 32 || x > 127)
|
||||||
if (x in invalids)
|
if (x in invalids)
|
||||||
invalids[string[i]].push(i)
|
invalids[string[i]].push(i)
|
||||||
else
|
else
|
||||||
invalids[string[i]] = [i];
|
invalids[string[i]] = [i];
|
||||||
}
|
}
|
||||||
if (Object.keys(invalids).length)
|
if (Object.keys(invalids).length)
|
||||||
return invalids;
|
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);
|
let chars = validateASCII(string, false);
|
||||||
if (chars === true)
|
if (chars === true)
|
||||||
return string;
|
return string;
|
||||||
@ -291,9 +420,9 @@
|
|||||||
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||||
mode = mode.toLowerCase();
|
mode = mode.toLowerCase();
|
||||||
if (mode === "hard-unicode")
|
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")
|
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")
|
else if (mode === "hard-remove")
|
||||||
convertor = c => "";
|
convertor = c => "";
|
||||||
else if (mode === "soft-remove")
|
else if (mode === "soft-remove")
|
||||||
@ -305,7 +434,7 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
floCrypto.revertUnicode = function(string) {
|
floCrypto.revertUnicode = function (string) {
|
||||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
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*/
|
/* General functions for FLO Dapps*/
|
||||||
//'use strict';
|
'use strict';
|
||||||
const floDapps = EXPORTS;
|
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() {
|
function initIndexedDB() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var obs_g = {
|
var obs_g = {
|
||||||
@ -21,6 +181,7 @@
|
|||||||
credentials: {},
|
credentials: {},
|
||||||
//for Dapps
|
//for Dapps
|
||||||
subAdmins: {},
|
subAdmins: {},
|
||||||
|
trustedIDs: {},
|
||||||
settings: {},
|
settings: {},
|
||||||
appObjects: {},
|
appObjects: {},
|
||||||
generalData: {},
|
generalData: {},
|
||||||
@ -28,41 +189,41 @@
|
|||||||
}
|
}
|
||||||
//add other given objectStores
|
//add other given objectStores
|
||||||
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
||||||
for (o in initIndexedDB.appObs)
|
for (let o in initIndexedDB.appObs)
|
||||||
if (!(o in obs_a))
|
if (!(o in obs_a))
|
||||||
obs_a[o] = initIndexedDB.appObs[o]
|
obs_a[o] = initIndexedDB.appObs[o]
|
||||||
Promise.all([
|
Promise.all([
|
||||||
compactIDB.initDB(floGlobals.application, obs_a),
|
compactIDB.initDB(DEFAULT.application, obs_a),
|
||||||
compactIDB.initDB("floDapps", obs_g)
|
compactIDB.initDB(DEFAULT.root, obs_g)
|
||||||
]).then(result => {
|
]).then(result => {
|
||||||
compactIDB.setDefaultDB(floGlobals.application)
|
compactIDB.setDefaultDB(DEFAULT.application)
|
||||||
resolve("IndexedDB App Storage Initated Successfully")
|
resolve("IndexedDB App Storage Initated Successfully")
|
||||||
}).catch(error => reject(error));
|
}).catch(error => reject(error));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function initUserDB(floID) {
|
function initUserDB() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var obs = {
|
var obs = {
|
||||||
contacts: {},
|
contacts: {},
|
||||||
pubKeys: {},
|
pubKeys: {},
|
||||||
messages: {}
|
messages: {}
|
||||||
}
|
}
|
||||||
compactIDB.initDB(`floDapps#${floID}`, obs).then(result => {
|
compactIDB.initDB(user.db_name, obs).then(result => {
|
||||||
resolve("UserDB Initated Successfully")
|
resolve("UserDB Initated Successfully")
|
||||||
}).catch(error => reject('Init userDB failed'));
|
}).catch(error => reject('Init userDB failed'));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadUserDB(floID) {
|
function loadUserDB() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var loadData = ["contacts", "pubKeys", "messages"]
|
var loadData = ["contacts", "pubKeys", "messages"]
|
||||||
var promises = []
|
var promises = []
|
||||||
for (var i = 0; i < loadData.length; i++)
|
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 => {
|
Promise.all(promises).then(results => {
|
||||||
for (var i = 0; i < loadData.length; i++)
|
for (var i = 0; i < loadData.length; i++)
|
||||||
floGlobals[loadData[i]] = results[i]
|
user[loadData[i]] = results[i]
|
||||||
resolve("Loaded Data from userDB")
|
resolve("Loaded Data from userDB")
|
||||||
}).catch(error => reject('Load userDB failed'))
|
}).catch(error => reject('Load userDB failed'))
|
||||||
})
|
})
|
||||||
@ -72,7 +233,7 @@
|
|||||||
|
|
||||||
startUpFunctions.push(function readSupernodeListFromAPI() {
|
startUpFunctions.push(function readSupernodeListFromAPI() {
|
||||||
return new Promise((resolve, reject) => {
|
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, {
|
floBlockchainAPI.readData(floCloudAPI.SNStorageID, {
|
||||||
ignoreOld: lastTx,
|
ignoreOld: lastTx,
|
||||||
sentOnly: true,
|
sentOnly: true,
|
||||||
@ -80,14 +241,20 @@
|
|||||||
}).then(result => {
|
}).then(result => {
|
||||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||||
var content = JSON.parse(result.data[i]).SuperNodeStorage;
|
var content = JSON.parse(result.data[i]).SuperNodeStorage;
|
||||||
for (sn in content.removeNodes)
|
for (let sn in content.removeNodes)
|
||||||
compactIDB.removeData("supernodes", sn, "floDapps");
|
compactIDB.removeData("supernodes", sn, DEFAULT.root);
|
||||||
for (sn in content.newNodes)
|
for (let sn in content.newNodes)
|
||||||
compactIDB.writeData("supernodes", content.newNodes[sn], sn, "floDapps");
|
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.writeData("lastTx", result.totalTxs, floCloudAPI.SNStorageID, DEFAULT.root);
|
||||||
compactIDB.readAllData("supernodes", "floDapps").then(result => {
|
compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => {
|
||||||
floCloudAPI.init(result)
|
floCloudAPI.init(nodes)
|
||||||
.then(result => resolve("Loaded Supernode list\n" + result))
|
.then(result => resolve("Loaded Supernode list\n" + result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
})
|
})
|
||||||
@ -98,14 +265,14 @@
|
|||||||
|
|
||||||
startUpFunctions.push(function readAppConfigFromAPI() {
|
startUpFunctions.push(function readAppConfigFromAPI() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
compactIDB.readData("lastTx", `${floGlobals.application}|${floGlobals.adminID}`, "floDapps").then(lastTx => {
|
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
|
||||||
floBlockchainAPI.readData(floGlobals.adminID, {
|
floBlockchainAPI.readData(DEFAULT.adminID, {
|
||||||
ignoreOld: lastTx,
|
ignoreOld: lastTx,
|
||||||
sentOnly: true,
|
sentOnly: true,
|
||||||
pattern: floGlobals.application
|
pattern: DEFAULT.application
|
||||||
}).then(result => {
|
}).then(result => {
|
||||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
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")
|
if (!content || typeof content !== "object")
|
||||||
continue;
|
continue;
|
||||||
if (Array.isArray(content.removeSubAdmin))
|
if (Array.isArray(content.removeSubAdmin))
|
||||||
@ -114,15 +281,21 @@
|
|||||||
if (Array.isArray(content.addSubAdmin))
|
if (Array.isArray(content.addSubAdmin))
|
||||||
for (var k = 0; k < content.addSubAdmin.length; k++)
|
for (var k = 0; k < content.addSubAdmin.length; k++)
|
||||||
compactIDB.writeData("subAdmins", true, content.addSubAdmin[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)
|
if (content.settings)
|
||||||
for (let l in content.settings)
|
for (let l in content.settings)
|
||||||
compactIDB.writeData("settings", content.settings[l], l)
|
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 => {
|
compactIDB.readAllData("subAdmins").then(result => {
|
||||||
floGlobals.subAdmins = Object.keys(result);
|
subAdmins = Object.keys(result);
|
||||||
compactIDB.readAllData("settings").then(result => {
|
compactIDB.readAllData("settings").then(result => {
|
||||||
floGlobals.settings = result;
|
settings = result;
|
||||||
resolve("Read app configuration from blockchain");
|
resolve("Read app configuration from blockchain");
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -186,7 +359,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
|
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
|
||||||
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||||
if (indexArr) {
|
if (indexArr) {
|
||||||
readSharesFromIDB(JSON.parse(indexArr))
|
readSharesFromIDB(JSON.parse(indexArr))
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
@ -197,7 +370,7 @@
|
|||||||
if (!result)
|
if (!result)
|
||||||
return reject("Empty Private Key")
|
return reject("Empty Private Key")
|
||||||
var floID = floCrypto.getFloID(result)
|
var floID = floCrypto.getFloID(result)
|
||||||
if (!floID || !floCrypto.validateAddr(floID))
|
if (!floID || !floCrypto.validateFloID(floID))
|
||||||
return reject("Invalid Private Key")
|
return reject("Invalid Private Key")
|
||||||
privKey = result;
|
privKey = result;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@ -210,7 +383,7 @@
|
|||||||
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
|
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
|
||||||
writeSharesToIDB(shares).then(resultIndexes => {
|
writeSharesToIDB(shares).then(resultIndexes => {
|
||||||
//store index keys in localStorage
|
//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
|
//also add a dummy privatekey to the IDB
|
||||||
var randomPrivKey = floCrypto.generateNewID().privKey
|
var randomPrivKey = floCrypto.generateNewID().privKey
|
||||||
var randomThreshold = floCrypto.randInt(10, 20)
|
var randomThreshold = floCrypto.randInt(10, 20)
|
||||||
@ -242,9 +415,14 @@
|
|||||||
getPrivateKeyCredentials().then(key => {
|
getPrivateKeyCredentials().then(key => {
|
||||||
checkIfPinRequired(key).then(privKey => {
|
checkIfPinRequired(key).then(privKey => {
|
||||||
try {
|
try {
|
||||||
myPrivKey = privKey
|
user_public = floCrypto.getPubKeyHex(privKey);
|
||||||
myPubKey = floCrypto.getPubKeyHex(myPrivKey)
|
user_id = floCrypto.getAddress(privKey);
|
||||||
myFloID = floCrypto.getFloID(myPubKey)
|
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')
|
resolve('Login Credentials loaded successful')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@ -289,7 +467,7 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
floDapps.launchStartUp = function() {
|
floDapps.launchStartUp = function () {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
initIndexedDB().then(log => {
|
initIndexedDB().then(log => {
|
||||||
console.log(log)
|
console.log(log)
|
||||||
@ -305,8 +483,8 @@
|
|||||||
});
|
});
|
||||||
let p2 = new Promise((res, rej) => {
|
let p2 = new Promise((res, rej) => {
|
||||||
callAndLog(getCredentials()).then(r => {
|
callAndLog(getCredentials()).then(r => {
|
||||||
callAndLog(initUserDB(myFloID)).then(r => {
|
callAndLog(initUserDB()).then(r => {
|
||||||
callAndLog(loadUserDB(myFloID))
|
callAndLog(loadUserDB())
|
||||||
.then(r => res(true))
|
.then(r => res(true))
|
||||||
.catch(e => rej(false))
|
.catch(e => rej(false))
|
||||||
}).catch(e => rej(false))
|
}).catch(e => rej(false))
|
||||||
@ -315,7 +493,10 @@
|
|||||||
Promise.all([p1, p2])
|
Promise.all([p1, p2])
|
||||||
.then(r => resolve('App Startup finished successful'))
|
.then(r => resolve('App Startup finished successful'))
|
||||||
.catch(e => reject('App Startup failed'))
|
.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.setAppObjectStores = appObs => initIndexedDB.appObs = appObs;
|
||||||
|
|
||||||
floDapps.storeContact = function(floID, name) {
|
floDapps.storeContact = function (floID, name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.validateAddr(floID))
|
if (!floCrypto.validateAddr(floID))
|
||||||
return reject("Invalid floID!")
|
return reject("Invalid floID!")
|
||||||
compactIDB.writeData("contacts", name, floID, `floDapps#${myFloID}`).then(result => {
|
compactIDB.writeData("contacts", name, floID, user.db_name).then(result => {
|
||||||
floGlobals.contacts[floID] = name;
|
user.contacts[floID] = name;
|
||||||
resolve("Contact stored")
|
resolve("Contact stored")
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.storePubKey = function(floID, pubKey) {
|
floDapps.storePubKey = function (floID, pubKey) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (floID in floGlobals.pubKeys)
|
if (floID in user.pubKeys)
|
||||||
return resolve("pubKey already stored")
|
return resolve("pubKey already stored")
|
||||||
if (!floCrypto.validateAddr(floID))
|
if (!floCrypto.validateAddr(floID))
|
||||||
return reject("Invalid floID!")
|
return reject("Invalid floID!")
|
||||||
if (floCrypto.getFloID(pubKey) != floID)
|
if (!floCrypto.verifyPubKey(pubKey, floID))
|
||||||
return reject("Incorrect pubKey")
|
return reject("Incorrect pubKey")
|
||||||
compactIDB.writeData("pubKeys", pubKey, floID, `floDapps#${myFloID}`).then(result => {
|
compactIDB.writeData("pubKeys", pubKey, floID, user.db_name).then(result => {
|
||||||
floGlobals.pubKeys[floID] = pubKey;
|
user.pubKeys[floID] = pubKey;
|
||||||
resolve("pubKey stored")
|
resolve("pubKey stored")
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.sendMessage = function(floID, message) {
|
floDapps.sendMessage = function (floID, message) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let options = {
|
let options = {
|
||||||
receiverID: floID,
|
receiverID: floID,
|
||||||
application: "floDapps",
|
application: DEFAULT.root,
|
||||||
comment: floGlobals.application
|
comment: DEFAULT.application
|
||||||
}
|
}
|
||||||
if (floID in floGlobals.pubKeys)
|
if (floID in user.pubKeys)
|
||||||
message = floCrypto.encryptData(JSON.stringify(message), floGlobals.pubKeys[floID])
|
message = floCrypto.encryptData(JSON.stringify(message), user.pubKeys[floID])
|
||||||
floCloudAPI.sendApplicationData(message, "Message", options)
|
floCloudAPI.sendApplicationData(message, "Message", options)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.requestInbox = function(callback) {
|
floDapps.requestInbox = function (callback) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let lastVC = Object.keys(floGlobals.messages).sort().pop()
|
let lastVC = Object.keys(user.messages).sort().pop()
|
||||||
let options = {
|
let options = {
|
||||||
receiverID: myFloID,
|
receiverID: user.id,
|
||||||
application: "floDapps",
|
application: DEFAULT.root,
|
||||||
lowerVectorClock: lastVC + 1
|
lowerVectorClock: lastVC + 1
|
||||||
}
|
}
|
||||||
|
let privKey = raw_user.private;
|
||||||
options.callback = (d, e) => {
|
options.callback = (d, e) => {
|
||||||
for (let v in d) {
|
for (let v in d) {
|
||||||
try {
|
try {
|
||||||
if (d[v].message instanceof Object && "secret" in d[v].message)
|
if (d[v].message instanceof Object && "secret" in d[v].message)
|
||||||
d[v].message = floCrypto.decryptData(d[v].message, myPrivKey)
|
d[v].message = floCrypto.decryptData(d[v].message, privKey)
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
compactIDB.writeData("messages", d[v], v, `floDapps#${myFloID}`)
|
compactIDB.writeData("messages", d[v], v, user.db_name)
|
||||||
floGlobals.messages[v] = d[v]
|
user.messages[v] = d[v]
|
||||||
}
|
}
|
||||||
if (callback instanceof Function)
|
if (callback instanceof Function)
|
||||||
callback(d, e)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||||
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||||
@ -404,36 +586,58 @@
|
|||||||
if (!addList && !rmList && !settings)
|
if (!addList && !rmList && !settings)
|
||||||
return reject("No configuration change")
|
return reject("No configuration change")
|
||||||
var floData = {
|
var floData = {
|
||||||
[floGlobals.application]: {
|
[DEFAULT.application]: {
|
||||||
addSubAdmin: addList,
|
addSubAdmin: addList,
|
||||||
removeSubAdmin: rmList,
|
removeSubAdmin: rmList,
|
||||||
settings: settings
|
settings: settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var floID = floCrypto.getFloID(adminPrivKey)
|
var floID = floCrypto.getFloID(adminPrivKey)
|
||||||
if (floID != floGlobals.adminID)
|
if (floID != DEFAULT.adminID)
|
||||||
reject('Access Denied for Admin privilege')
|
reject('Access Denied for Admin privilege')
|
||||||
else
|
else
|
||||||
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
||||||
.then(result => resolve(['Updated App Configuration', result]))
|
.then(result => resolve(['Updated App Configuration', result]))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearCredentials = floDapps.clearCredentials = function() {
|
floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
compactIDB.clearData('credentials', floGlobals.application).then(result => {
|
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||||
localStorage.removeItem(`${floGlobals.application}#privKey`)
|
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||||
myPrivKey = myPubKey = myFloID = 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!")
|
resolve("privKey credentials deleted!")
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.deleteUserData = function(credentials = false) {
|
floDapps.deleteUserData = function (credentials = false) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let p = []
|
let p = []
|
||||||
p.push(compactIDB.deleteDB(`floDapps#${myFloID}`))
|
p.push(compactIDB.deleteDB(user.db_name))
|
||||||
if (credentials)
|
if (credentials)
|
||||||
p.push(clearCredentials())
|
p.push(clearCredentials())
|
||||||
Promise.all(p)
|
Promise.all(p)
|
||||||
@ -442,30 +646,30 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.deleteAppData = function() {
|
floDapps.deleteAppData = function () {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
compactIDB.deleteDB(floGlobals.application).then(result => {
|
compactIDB.deleteDB(DEFAULT.application).then(result => {
|
||||||
localStorage.removeItem(`${floGlobals.application}#privKey`)
|
localStorage.removeItem(`${DEFAULT.application}#privKey`)
|
||||||
myPrivKey = myPubKey = myFloID = undefined;
|
user.clear();
|
||||||
compactIDB.removeData('lastTx', `${floGlobals.application}|${floGlobals.adminID}`, 'floDapps')
|
compactIDB.removeData('lastTx', `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root)
|
||||||
.then(result => resolve("App database(local) deleted"))
|
.then(result => resolve("App database(local) deleted"))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.securePrivKey = function(pwd) {
|
floDapps.securePrivKey = function (pwd) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
let indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||||
if (!indexArr)
|
if (!indexArr)
|
||||||
return reject("PrivKey not found");
|
return reject("PrivKey not found");
|
||||||
indexArr = JSON.parse(indexArr)
|
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 threshold = indexArr.length;
|
||||||
let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold)
|
let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold)
|
||||||
let promises = [];
|
let promises = [];
|
||||||
let overwriteFn = (share, index) =>
|
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++)
|
for (var i = 0; i < threshold; i++)
|
||||||
promises.push(overwriteFn(shares[i], indexArr[i]));
|
promises.push(overwriteFn(shares[i], indexArr[i]));
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
@ -474,8 +678,8 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floDapps.verifyPin = function(pin = null) {
|
floDapps.verifyPin = function (pin = null) {
|
||||||
const readSharesFromIDB = function(indexArr) {
|
const readSharesFromIDB = function (indexArr) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var promises = []
|
var promises = []
|
||||||
for (var i = 0; i < indexArr.length; i++)
|
for (var i = 0; i < indexArr.length; i++)
|
||||||
@ -494,7 +698,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||||
console.info(indexArr)
|
console.info(indexArr)
|
||||||
if (!indexArr)
|
if (!indexArr)
|
||||||
reject('No login credentials found')
|
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)
|
var fk = floCloudAPI.util.filterKey(type, options)
|
||||||
vectorClock = vectorClock || getNextGeneralData[fk] || '0';
|
vectorClock = vectorClock || getNextGeneralData[fk] || '0';
|
||||||
var filteredResult = {}
|
var filteredResult = {}
|
||||||
@ -535,7 +739,7 @@
|
|||||||
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||||
}
|
}
|
||||||
if (options.decrypt) {
|
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))
|
if (!Array.isArray(decryptionKey))
|
||||||
decryptionKey = [decryptionKey];
|
decryptionKey = [decryptionKey];
|
||||||
for (let f in filteredResult) {
|
for (let f in filteredResult) {
|
||||||
@ -547,10 +751,10 @@
|
|||||||
let tmp = floCrypto.decryptData(data.message, key)
|
let tmp = floCrypto.decryptData(data.message, key)
|
||||||
data.message = JSON.parse(tmp)
|
data.message = JSON.parse(tmp)
|
||||||
break;
|
break;
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop();
|
getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop();
|
||||||
@ -561,14 +765,14 @@
|
|||||||
|
|
||||||
syncData.oldDevice = () => new Promise((resolve, reject) => {
|
syncData.oldDevice = () => new Promise((resolve, reject) => {
|
||||||
let sync = {
|
let sync = {
|
||||||
contacts: floGlobals.contacts,
|
contacts: user.contacts,
|
||||||
pubKeys: floGlobals.pubKeys,
|
pubKeys: user.pubKeys,
|
||||||
messages: floGlobals.messages
|
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 = {
|
let options = {
|
||||||
receiverID: myFloID,
|
receiverID: user.id,
|
||||||
application: "floDapps"
|
application: DEFAULT.root
|
||||||
}
|
}
|
||||||
floCloudAPI.sendApplicationData(message, "syncData", options)
|
floCloudAPI.sendApplicationData(message, "syncData", options)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
@ -577,20 +781,20 @@
|
|||||||
|
|
||||||
syncData.newDevice = () => new Promise((resolve, reject) => {
|
syncData.newDevice = () => new Promise((resolve, reject) => {
|
||||||
var options = {
|
var options = {
|
||||||
receiverID: myFloID,
|
receiverID: user.id,
|
||||||
senderID: myFloID,
|
senderID: user.id,
|
||||||
application: "floDapps",
|
application: DEFAULT.root,
|
||||||
mostRecent: true,
|
mostRecent: true,
|
||||||
}
|
}
|
||||||
floCloudAPI.requestApplicationData("syncData", options).then(response => {
|
floCloudAPI.requestApplicationData("syncData", options).then(response => {
|
||||||
let vc = Object.keys(response).sort().pop()
|
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 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 => {
|
["contacts", "pubKeys", "messages"].forEach(c => {
|
||||||
for (let i in sync[c]) {
|
for (let i in sync[c]) {
|
||||||
store(i, sync[c][i], c)
|
store(i, sync[c][i], c)
|
||||||
floGlobals[c][i] = sync[c][i]
|
user[c][i] = sync[c][i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Promise.all(promises)
|
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*/
|
/* Token Operator to send/receive tokens via blockchain using API calls*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const tokenAPI = EXPORTS;
|
const tokenAPI = EXPORTS;
|
||||||
|
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
||||||
currency: "rupee"
|
currency: floGlobals.currency || "rupee"
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperties(tokenAPI, {
|
Object.defineProperties(tokenAPI, {
|
||||||
@ -27,9 +27,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetch_api = tokenAPI.fetch = function(apicall) {
|
const fetch_api = tokenAPI.fetch = function (apicall) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log(DEFAULT.apiURL + apicall);
|
console.debug(DEFAULT.apiURL + apicall);
|
||||||
fetch(DEFAULT.apiURL + apicall).then(response => {
|
fetch(DEFAULT.apiURL + apicall).then(response => {
|
||||||
if (response.ok)
|
if (response.ok)
|
||||||
response.json().then(data => resolve(data));
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
||||||
.then(result => resolve(result.balance || 0))
|
.then(result => resolve(result.balance || 0))
|
||||||
@ -47,7 +47,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenAPI.getTx = function(txID) {
|
tokenAPI.getTx = function (txID) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
|
fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
|
||||||
if (res.result === "error")
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let senderID = floCrypto.getFloID(privKey);
|
let senderID = floCrypto.getFloID(privKey);
|
||||||
if (typeof amount !== "number" || isNaN(amount) || amount <= 0)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
const util = tokenAPI.util = {};
|
const util = tokenAPI.util = {};
|
||||||
|
|
||||||
util.parseTxData = function(txData) {
|
util.parseTxData = function (txData) {
|
||||||
let parsedData = {};
|
let parsedData = {};
|
||||||
for (let p in txData.parsedFloData)
|
for (let p in txData.parsedFloData)
|
||||||
parsedData[p] = txData.parsedFloData[p];
|
parsedData[p] = txData.parsedFloData[p];
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
</script>
|
</script>
|
||||||
<script src="lib.js"></script>
|
<script src="lib.js"></script>
|
||||||
<script src="floCrypto.js"></script>
|
<script src="floCrypto.js"></script>
|
||||||
|
<script src="btcOperator.js"></script>
|
||||||
<script src="floBlockchainAPI.js"></script>
|
<script src="floBlockchainAPI.js"></script>
|
||||||
<script src="floTokenAPI.js"></script>
|
<script src="floTokenAPI.js"></script>
|
||||||
<script src="compactIDB.js"></script>
|
<script src="compactIDB.js"></script>
|
||||||
@ -24,7 +25,6 @@
|
|||||||
//floDapps.addStartUpFunction('Sample', Promised Function)
|
//floDapps.addStartUpFunction('Sample', Promised Function)
|
||||||
//floDapps.setAppObjectStores({sampleObs1:{}, sampleObs2:{options{autoIncrement:true, keyPath:'SampleKey'}, Indexes:{sampleIndex:{}}}})
|
//floDapps.setAppObjectStores({sampleObs1:{}, sampleObs2:{options{autoIncrement:true, keyPath:'SampleKey'}, Indexes:{sampleIndex:{}}}})
|
||||||
//floDapps.setCustomPrivKeyInput( () => { FUNCTION BODY *must resolve private key* } )
|
//floDapps.setCustomPrivKeyInput( () => { FUNCTION BODY *must resolve private key* } )
|
||||||
|
|
||||||
floDapps.launchStartUp().then(result => {
|
floDapps.launchStartUp().then(result => {
|
||||||
console.log(result)
|
console.log(result)
|
||||||
alert(`Welcome FLO_ID: ${myFloID}`)
|
alert(`Welcome FLO_ID: ${myFloID}`)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user