diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..168b657 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.tmp* \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..d3f4c4c --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sai Raj + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/index.html b/index.html index 0d30089..0850777 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@ - + - - - - - - - + + + + + + + - + - \ No newline at end of file + diff --git a/scripts/btcOperator.js b/scripts/btcOperator.js index bb0b7ca..2abb80f 100644 --- a/scripts/btcOperator.js +++ b/scripts/btcOperator.js @@ -1,29 +1,43 @@ -(function (EXPORTS) { //btcOperator v1.0.12 +(function (EXPORTS) { //btcOperator v1.1.3b /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; //This library uses API provided by chain.so (https://chain.so/) - const URL = "https://chain.so/api/v2/"; + const URL = "https://blockchain.info/"; - const fetch_api = btcOperator.fetch = function (api) { + const DUST_AMT = 546, + MIN_FEE_UPDATE = 219; + + const fetch_api = btcOperator.fetch = function (api, json_res = true) { return new Promise((resolve, reject) => { console.debug(URL + api); fetch(URL + api).then(response => { - response.json() - .then(result => result.status === "success" ? resolve(result) : reject(result)) - .catch(error => reject(error)) + 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(parseFloat((result.regular / SATOSHI_IN_BTC).toFixed(8)))) + .then(result => resolve(util.Sat_to_BTC(result.regular))) .catch(error => reject(error)); else reject(response); @@ -102,64 +116,101 @@ if (!addr) return undefined; let type = coinjs.addressDecode(addr).type; - if (["standard", "multisig", "bech32"].includes(type)) + if (["standard", "multisig", "bech32", "multisigBech32"].includes(type)) return type; else return false; } - btcOperator.multiSigAddress = function (pubKeys, minRequired) { + 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"; - return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired); + if (bech32) + return coinjs.pubkeys2MultisigAddressBech32(pubKeys, minRequired); + else + return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired); + } + + btcOperator.decodeRedeemScript = function (redeemScript, bech32 = true) { + let script = coinjs.script(); + let decoded = (bech32) ? + script.decodeRedeemScriptBech32(redeemScript) : + script.decodeRedeemScript(redeemScript); + if (!decoded) + return null; + return { + address: decoded.address, + pubKeys: decoded.pubkeys, + redeemScript: decoded.redeemscript, + required: decoded.signaturesRequired + } + } //convert from one blockchain to another blockchain (target version) btcOperator.convert = {}; btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) { - let keyHex = decodeLegacy(source_wif).hex; + let keyHex = util.decodeLegacy(source_wif).hex; if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex)) return null; else - return encodeLegacy(keyHex, target_version); + return util.encodeLegacy(keyHex, target_version); } btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) { - let rawHex = decodeLegacy(source_addr).hex; + let rawHex = util.decodeLegacy(source_addr).hex; if (!rawHex) return null; else - return encodeLegacy(rawHex, target_version); + return util.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; + let rawHex = util.decodeLegacy(source_addr).hex; if (!rawHex) return null; else - return encodeBech32(rawHex, target_version, target_hrp); + return util.encodeBech32(rawHex, target_version, target_hrp); } btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { - let rawHex = decodeBech32(source_addr).hex; + let rawHex = util.decodeBech32(source_addr).hex; if (!rawHex) return null; else - return encodeBech32(rawHex, target_version, target_hrp); + return util.encodeBech32(rawHex, target_version, target_hrp); } btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) { - let rawHex = decodeBech32(source_addr).hex; + let rawHex = util.decodeBech32(source_addr).hex; if (!rawHex) return null; else - return encodeLegacy(rawHex, target_version); + return util.encodeLegacy(rawHex, target_version); } - function decodeLegacy(source) { + btcOperator.convert.multisig2multisig = function (source_addr, target_version = coinjs.multisig) { + let rawHex = util.decodeLegacy(source_addr).hex; + if (!rawHex) + return null; + else + return util.encodeLegacy(rawHex, target_version); + } + + btcOperator.convert.bech2multisig = function (source_addr, target_version = coinjs.multisig) { + let rawHex = util.decodeBech32(source_addr).hex; + if (!rawHex) + return null; + else { + rawHex = Crypto.util.bytesToHex(ripemd160(Crypto.util.hexToBytes(rawHex), { asBytes: true })); + return util.encodeLegacy(rawHex, target_version); + } + } + + util.decodeLegacy = function (source) { var decode = coinjs.base58decode(source); var raw = decode.slice(0, decode.length - 4), checksum = decode.slice(decode.length - 4); @@ -169,7 +220,7 @@ asBytes: true }); if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) - return null; + return false; let version = raw.shift(); return { version: version, @@ -177,7 +228,7 @@ } } - function encodeLegacy(hex, version) { + util.encodeLegacy = function (hex, version) { var bytes = Crypto.util.hexToBytes(hex); bytes.unshift(version); var hash = Crypto.SHA256(Crypto.SHA256(bytes, { @@ -189,10 +240,10 @@ return coinjs.base58encode(bytes.concat(checksum)); } - function decodeBech32(source) { + util.decodeBech32 = function (source) { let decode = coinjs.bech32_decode(source); if (!decode) - return null; + return false; var raw = decode.data; let version = raw.shift(); raw = coinjs.bech32_convert(raw, 5, 8, false); @@ -203,7 +254,7 @@ } } - function encodeBech32(hex, version, hrp) { + util.encodeBech32 = function (hex, version, hrp) { var bytes = Crypto.util.hexToBytes(hex); bytes = coinjs.bech32_convert(bytes, 8, 5, true); bytes.unshift(version) @@ -213,8 +264,8 @@ //BTC blockchain APIs btcOperator.getBalance = addr => new Promise((resolve, reject) => { - fetch_api(`get_address_balance/BTC/${addr}`) - .then(result => resolve(parseFloat(result.data.confirmed_balance))) + fetch_api(`q/addressbalance/${addr}`) + .then(result => resolve(util.Sat_to_BTC(result))) .catch(error => reject(error)) }); @@ -222,11 +273,13 @@ 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) { @@ -249,6 +302,8 @@ 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__": @@ -269,6 +324,8 @@ 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: @@ -299,7 +356,7 @@ parameters.privkeys[i] = coinjs.privkey2wif(key); }); if (invalids.length) - throw "Invalid keys:" + invalids; + throw "Invalid private key for address:" + invalids; } //receiver-ids (and change-id) if (!Array.isArray(parameters.receivers)) @@ -307,8 +364,8 @@ parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null); if (invalids.length) throw "Invalid receivers:" + invalids; - if (parameters.change_addr && !validateAddress(parameters.change_addr)) - throw "Invalid change_address:" + parameters.change_addr; + 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; @@ -323,18 +380,37 @@ return parameters; } - function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr) { + 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_addr); - addInputs(tx, senders, redeemScripts, total_amount, fee, output_size).then(result => { - if (result.change_amount > 0) - tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi - else - tx.outs.pop(); //remove the change output if no change_amount + 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"); + + } + //remove all output with value less than DUST amount + let filtered_outputs = [], dust_value = 0; + tx.outs.forEach(o => o.value >= DUST_AMT ? filtered_outputs.push(o) : dust_value += o.value); + tx.outs = filtered_outputs; + //update result values + result.fee += util.Sat_to_BTC(dust_value); result.output_size = output_size; - result.output_amount = total_amount; + 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); @@ -342,10 +418,10 @@ }) } - function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size) { + 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, total_amount + fee, false).then(result => { + addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => { result.fee = fee; resolve(result); }).catch(error => reject(error)) @@ -353,7 +429,10 @@ get_fee_rate().then(fee_rate => { let net_fee = BASE_TX_SIZE * fee_rate; net_fee += (output_size * fee_rate); - addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate).then(result => { + (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); @@ -381,30 +460,31 @@ 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(`get_tx_unspent/BTC/${addr}`).then(result => { - let utxos = result.data.txs; - console.debug("add-utxo", addr, rs, required_amount, utxos); + 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_hex; - else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) { - //redeemScript for segwit/bech32 + 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 * SATOSHI_IN_BTC).toFixed(0), 8)); + s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0), 8)); script = Crypto.util.bytesToHex(s.buffer); - } else //redeemScript for multisig + } else //redeemScript for multisig (segwit) script = rs; - tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee + 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 += parseFloat(utxos[i].value); - required_amount -= parseFloat(utxos[i].value); + 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; } @@ -416,14 +496,14 @@ }) } - function addOutputs(tx, receivers, amounts, change_addr) { + 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_addr, 0); - size += _sizePerOutput(change_addr); + tx.addoutput(change_address, 0); + size += _sizePerOutput(change_address); return size; } @@ -464,9 +544,109 @@ } */ - btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee, change_addr = null) { + function tx_fetch_for_editing(tx) { return new Promise((resolve, reject) => { - createSignedTx(senders, privkeys, receivers, amounts, fee, change_addr).then(result => { + if (typeof tx == 'string' && /^[0-9a-f]{64}$/i.test(tx)) { //tx is txid + getTx.hex(tx) + .then(txhex => resolve(deserializeTx(txhex))) + .catch(error => reject(error)) + } else resolve(deserializeTx(tx)); + }) + } + + + btcOperator.editFee = function (tx_hex, new_fee, private_keys, change_only = true) { + return new Promise((resolve, reject) => { + if (!Array.isArray(private_keys)) + private_keys = [private_keys]; + tx_fetch_for_editing(tx_hex).then(tx => { + parseTransaction(tx).then(tx_parsed => { + if (tx_parsed.fee >= new_fee) + return reject("Fees can only be increased"); + + //editable addresses in output values (for fee increase) + var edit_output_address = new Set(); + if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee + tx_parsed.inputs.forEach(inp => edit_output_address.add(inp.address)); + else if (change_only === false) //allow all output values to be edited + tx_parsed.outputs.forEach(out => edit_output_address.add(out.address)); + else if (typeof change_only == 'string') // allow only given receiver id output to be edited + edit_output_address.add(change_only); + else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited + change_only.forEach(id => edit_output_address.add(id)); + + //edit output values to increase fee + let inc_fee = util.BTC_to_Sat(new_fee - tx_parsed.fee); + if (inc_fee < MIN_FEE_UPDATE) + return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`); + for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order + if (edit_output_address.has(tx_parsed.outputs[i].address)) { + let current_value = tx.outs[i].value; + if (current_value instanceof BigInteger) //convert BigInteger class to inv value + current_value = current_value.intValue(); + //edit the value as required + if (current_value > inc_fee) { + tx.outs[i].value = current_value - inc_fee; + inc_fee = 0; + } else { + inc_fee -= current_value; + tx.outs[i].value = 0; + } + } + if (inc_fee > 0) { + let max_possible_fee = util.BTC_to_Sat(new_fee) - inc_fee; //in satoshi + return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`); + } + tx.outs = tx.outs.filter(o => o.value >= DUST_AMT); //remove all output with value less than DUST amount + + //remove existing signatures and reset the scripts + let wif_keys = []; + for (let i in tx.ins) { + var addr = tx_parsed.inputs[i].address, + value = util.BTC_to_Sat(tx_parsed.inputs[i].value); + let addr_decode = coinjs.addressDecode(addr); + //find the correct key for addr + var privKey = private_keys.find(pk => verifyKey(addr, pk)); + if (!privKey) + return reject(`Private key missing for ${addr}`); + //find redeemScript (if any) + const rs = _redeemScript(addr, privKey); + rs === false ? wif_keys.unshift(privKey) : wif_keys.push(privKey); //sorting private-keys (wif) + //reset the script for re-signing + var script; + if (!rs || !rs.length) { + //legacy script (derive from address) + let s = coinjs.script(); + s.writeOp(118); //OP_DUP + s.writeOp(169); //OP_HASH160 + s.writeBytes(addr_decode.bytes); + s.writeOp(136); //OP_EQUALVERIFY + s.writeOp(172); //OP_CHECKSIG + script = Crypto.util.bytesToHex(s.buffer); + } else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_decode.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(value.toFixed(0), 8)); + script = Crypto.util.bytesToHex(s.buffer); + } else //redeemScript for multisig (segwit) + script = rs; + tx.ins[i].script = coinjs.script(script); + } + tx.witness = false; //remove all witness signatures + console.debug("Unsigned:", tx.serialize()); + //re-sign the transaction + new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF + resolve(tx.serialize()); + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + + 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)) @@ -475,7 +655,7 @@ }) } - const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, change_addr = null) { + const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { try { ({ @@ -489,7 +669,7 @@ receivers, amounts, fee, - change_addr + change_address: options.change_address })); } catch (e) { return reject(e) @@ -504,17 +684,17 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(result => { + 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 + new Set(wif_keys).forEach(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, change_addr = null) { + btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { try { ({ @@ -526,7 +706,7 @@ receivers, amounts, fee, - change_addr + change_address: options.change_address })); } catch (e) { return reject(e) @@ -535,7 +715,7 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(result => { + 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); @@ -543,14 +723,17 @@ }) } - btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null) { + btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { //validate tx parameters - if (validateAddress(sender) !== "multisig") + let addr_type = validateAddress(sender); + if (!(["multisig", "multisigBech32"].includes(addr_type))) return reject("Invalid sender (multisig):" + sender); else { let script = coinjs.script(); - let decode = script.decodeRedeemScript(redeemScript); + let decode = (addr_type == "multisig") ? + script.decodeRedeemScript(redeemScript) : + script.decodeRedeemScriptBech32(redeemScript); if (!decode || decode.address !== sender) return reject("Invalid redeem-script"); } @@ -561,13 +744,14 @@ } = validateTxParameters({ receivers, amounts, - fee + fee, + change_address: options.change_address })); } catch (e) { return reject(e) } //create transaction - createTransaction([sender], [redeemScript], receivers, amounts, fee, sender).then(result => { + 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); @@ -604,10 +788,10 @@ let n = []; for (let i in tx.ins) { var s = tx.extractScriptKey(i); - if (s['type'] !== 'multisig') + 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); + 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'], @@ -627,24 +811,27 @@ btcOperator.checkIfSameTx = function (tx1, tx2) { tx1 = deserializeTx(tx1); tx2 = deserializeTx(tx2); + //compare input and output length if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length) return false; + //compare inputs for (let i = 0; i < tx1.ins.length; i++) if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index) return false; - for (let i = 0; i < tx2.ins.length; i++) + //compare outputs + for (let i = 0; i < tx1.outs.length; i++) if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer)) return false; return true; } const getTxOutput = (txid, i) => new Promise((resolve, reject) => { - fetch_api(`get_tx_outputs/BTC/${txid}/${i}`) - .then(result => resolve(result.data.outputs)) + fetch_api(`rawtx/${txid}`) + .then(result => resolve(result.out[i])) .catch(error => reject(error)) }); - btcOperator.parseTransaction = function (tx) { + const parseTransaction = btcOperator.parseTransaction = function (tx) { return new Promise((resolve, reject) => { tx = deserializeTx(tx); let result = {}; @@ -654,8 +841,8 @@ 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.address, - value: parseFloat(inp.value) + address: inp.addr, + value: util.Sat_to_BTC(inp.value) })); let signed = checkSigned(tx, false); result.inputs.forEach((inp, i) => inp.signed = signed[i]); @@ -663,18 +850,18 @@ result.outputs = tx.outs.map(out => { var address; switch (out.script.chunks[0]) { - case 0: //bech32 - address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp); + case 0: //bech32, multisig-bech32 + address = util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp); break; - case 169: //multisig, segwit - address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig); + case 169: //segwit, multisig-segwit + address = util.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); + address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub); } return { address, - value: parseFloat(out.value / SATOSHI_IN_BTC) + value: util.Sat_to_BTC(out.value) } }); //Parse Totals @@ -695,22 +882,115 @@ return Crypto.util.bytesToHex(txid); } - btcOperator.getTx = txid => new Promise((resolve, reject) => { - fetch_api(`tx/BTC/${txid}`) - .then(result => resolve(result.data)) + const getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => { + fetch_api(`q/getblockcount`) + .then(result => resolve(result)) .catch(error => reject(error)) + }) + + const getTx = 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.getAddressData = addr => new Promise((resolve, reject) => { - fetch_api(`address/BTC/${addr}`) - .then(result => resolve(result.data)) + 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(`get_block/BTC/${block}`) - .then(result => resolve(result.data)) - .catch(error => reject(error)) + 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 = {}); \ No newline at end of file +})('object' === typeof module ? module.exports : window.btcOperator = {}); diff --git a/scripts/btcOperator.min.js b/scripts/btcOperator.min.js deleted file mode 100644 index 48f0253..0000000 --- a/scripts/btcOperator.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){function t(){return new Promise((e,t)=>{fetch("https://api.blockchain.info/mempool/fees").then(n=>{n.ok?n.json().then(t=>e(parseFloat((t.regular/m).toFixed(8)))).catch(e=>t(e)):t(n)}).catch(e=>t(e))})}function n(e){var t=coinjs.base58decode(e),n=t.slice(0,t.length-4),s=t.slice(t.length-4),r=Crypto.SHA256(Crypto.SHA256(n,{asBytes:!0}),{asBytes:!0});if(r[0]!=s[0]||r[1]!=s[1]||r[2]!=s[2]||r[3]!=s[3])return null;let i=n.shift();return{version:i,hex:Crypto.util.bytesToHex(n)}}function s(e,t){var n=Crypto.util.hexToBytes(e);n.unshift(t);var s=Crypto.SHA256(Crypto.SHA256(n,{asBytes:!0}),{asBytes:!0}),r=s.slice(0,4);return coinjs.base58encode(n.concat(r))}function r(e){let t=coinjs.bech32_decode(e);if(!t)return null;var n=t.data;let s=n.shift();return n=coinjs.bech32_convert(n,5,8,!1),{hrp:t.hrp,version:s,hex:Crypto.util.bytesToHex(n)}}function i(e,t,n){var s=Crypto.util.hexToBytes(e);return s=coinjs.bech32_convert(s,8,5,!0),s.unshift(t),coinjs.bech32_encode(n,s)}function o(e,t){let n=coinjs.addressDecode(e);switch(n.type){case"standard":return!1;case"multisig":return t?coinjs.segwitAddress(y.pubkey(t)).redeemscript:null;case"bech32":return n.redeemscript;default:return null}}function c(e,t){switch(coinjs.addressDecode(e).type){case"standard":return _+k;case"bech32":return _+A;case"multisig":switch(coinjs.script().decodeRedeemScript(t).type){case"segwit__":return _+T;case"multisig__":return _+C;default:return null}default:return null}}function a(e){switch(coinjs.addressDecode(e).type){case"standard":return B+F;case"bech32":return B+P;case"multisig":return B+S;default:return null}}function u(e){let t=[];if(e.senders&&(Array.isArray(e.senders)||(e.senders=[e.senders]),e.senders.forEach(e=>x(e)?null:t.push(e)),t.length))throw"Invalid senders:"+t;if(e.privkeys){if(Array.isArray(e.privkeys)||(e.privkeys=[e.privkeys]),e.senders.length!=e.privkeys.length)throw"Array length for senders and privkeys should be equal";if(e.senders.forEach((n,s)=>{let r=e.privkeys[s];w(n,r)||t.push(n),64===r.length&&(e.privkeys[s]=coinjs.privkey2wif(r))}),t.length)throw"Invalid keys:"+t}if(Array.isArray(e.receivers)||(e.receivers=[e.receivers]),e.receivers.forEach(e=>x(e)?null:t.push(e)),t.length)throw"Invalid receivers:"+t;if(e.change_addr&&!x(e.change_addr))throw"Invalid change_address:"+e.change_addr;if(("number"!=typeof e.fee||e.fee<=0)&&null!==e.fee)throw"Invalid fee:"+e.fee;if(Array.isArray(e.amounts)||(e.amounts=[e.amounts]),e.receivers.length!=e.amounts.length)throw"Array length for receivers and amounts should be equal";if(e.amounts.forEach(e=>"number"!=typeof e||e<=0?t.push(e):null),t.length)throw"Invalid amounts:"+t;return e}function l(e,t,n,s,r,i){return new Promise((o,c)=>{let a=parseFloat(s.reduce((e,t)=>e+t,0).toFixed(8));const u=coinjs.transaction();let l=p(u,n,s,i);d(u,e,t,a,r,l).then(e=>{e.change_amount>0?u.outs[u.outs.length-1].value=parseInt(e.change_amount*m):u.outs.pop(),e.output_size=l,e.output_amount=a,e.total_size=j+l+e.input_size,e.transaction=u,o(e)}).catch(e=>c(e))})}function d(e,n,s,r,i,o){return new Promise((c,a)=>{null!==i?h(e,n,s,r+i,!1).then(e=>{e.fee=i,c(e)}).catch(e=>a(e)):t().then(t=>{let i=j*t;i+=o*t,h(e,n,s,r+i,t).then(e=>{e.fee=parseFloat((i+e.input_size*t).toFixed(8)),e.fee_rate=t,c(e)}).catch(e=>a(e))}).catch(e=>a(e))})}function h(e,t,n,s,r,i={}){return new Promise((o,a)=>{if(s=parseFloat(s.toFixed(8)),void 0===i.n&&(i.n=0,i.input_size=0,i.input_amount=0),s<=0)return o({input_size:i.input_size,input_amount:i.input_amount,change_amount:-1*s});if(i.n>=t.length)return a("Insufficient Balance");let u=t[i.n],l=n[i.n],d=c(u,l);b(`get_tx_unspent/BTC/${u}`).then(c=>{let p=c.data.txs;console.debug("add-utxo",u,l,s,p);for(let t=0;t0;t++)if(p[t].confirmations){var f;if(l&&l.length)if(l.match(/^00/)&&44==l.length||40==l.length&&l.match(/^[a-f0-9]+$/gi)){let e=coinjs.script();e.writeBytes(Crypto.util.hexToBytes(l)),e.writeOp(0),e.writeBytes(coinjs.numToBytes((p[t].value*m).toFixed(0),8)),f=Crypto.util.bytesToHex(e.buffer)}else f=l;else f=p[t].script_hex;e.addinput(p[t].txid,p[t].output_no,f,4294967293),i.input_size+=d,i.input_amount+=parseFloat(p[t].value),s-=parseFloat(p[t].value),r&&(s+=d*r)}i.n+=1,h(e,t,n,s,r,i).then(e=>o(e)).catch(e=>a(e))}).catch(e=>a(e))})}function p(e,t,n,s){let r=0;for(let s in t)e.addoutput(t[s],n[s]),r+=a(t[s]);return e.addoutput(s,0),r+=a(s),r}function f(e){if("string"==typeof e||Array.isArray(e))try{e=coinjs.transaction().deserialize(e)}catch{throw"Invalid transaction hex"}else if("object"!=typeof e||"function"!=typeof e.sign)throw"Invalid transaction object";return e}const y=e,g="https://chain.so/api/v2/",b=y.fetch=function(e){return new Promise((t,n)=>{console.debug(g+e),fetch(g+e).then(e=>{e.json().then(e=>"success"===e.status?t(e):n(e)).catch(e=>n(e))}).catch(e=>n(e))})},m=1e8,v=y.broadcastTx=(e=>new Promise((t,n)=>{let s="https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction";fetch(s,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"rawtx="+e}).then(e=>{e.text().then(e=>{let s=e.match(/.*<\/result>/);if(s)if(s=s.pop().replace("","").replace("",""),"1"==s){let n=e.match(/.*<\/txid>/).pop().replace("","").replace("","");t(n)}else if("0"==s){let t=e.match(/.*<\/response>/).pop().replace("","").replace("","");n(decodeURIComponent(t.replace(/\+/g," ")))}else n(e);else n(e)}).catch(e=>n(e))}).catch(e=>n(e))}));Object.defineProperties(y,{newKeys:{get:()=>{let e=coinjs.newKeys();return e.segwitAddress=coinjs.segwitAddress(e.pubkey).address,e.bech32Address=coinjs.bech32Address(e.pubkey).address,e}},pubkey:{value:e=>e.length>=66?e:64==e.length?coinjs.newPubkey(e):coinjs.wif2pubkey(e).pubkey},address:{value:(e,t)=>coinjs.pubkey2address(y.pubkey(e),t)},segwitAddress:{value:e=>coinjs.segwitAddress(y.pubkey(e)).address},bech32Address:{value:e=>coinjs.bech32Address(y.pubkey(e)).address}}),coinjs.compressed=!0;const w=y.verifyKey=function(e,t){if(e&&t)switch(coinjs.addressDecode(e).type){case"standard":return y.address(t)===e;case"multisig":return y.segwitAddress(t)===e;case"bech32":return y.bech32Address(t)===e;default:return null}},x=y.validateAddress=function(e){if(!e)return;let t=coinjs.addressDecode(e).type;return!!["standard","multisig","bech32"].includes(t)&&t};y.multiSigAddress=function(e,t){if(!Array.isArray(e))throw"pubKeys must be an array of public keys";if(e.lengthnew Promise((t,n)=>{b(`get_address_balance/BTC/${e}`).then(e=>t(parseFloat(e.data.confirmed_balance))).catch(e=>n(e))}));const j=12,_=41,k=107,A=27,T=59,C=351,B=9,F=25,P=23,S=23;y.sendTx=function(e,t,n,s,r,i=null){return new Promise((o,c)=>{z(e,t,n,s,r,i).then(e=>{v(e.transaction.serialize()).then(e=>o(e)).catch(e=>c(e))}).catch(e=>c(e))})};const z=y.createSignedTx=function(e,t,n,s,r=null,i=null){return new Promise((c,a)=>{try{({senders:e,privkeys:t,receivers:n,amounts:s}=u({senders:e,privkeys:t,receivers:n,amounts:s,fee:r,change_addr:i}))}catch(e){return a(e)}let d=[],h=[];for(let n in e){let s=o(e[n],t[n]);d.push(s),!1===s?h.unshift(t[n]):h.push(t[n])}if(d.includes(null))return a("Unable to get redeem-script");l(e,d,n,s,r,i||e[0]).then(e=>{let t=e.transaction;console.debug("Unsigned:",t.serialize()),new Set(h).forEach(e=>console.debug("Signing key:",e,t.sign(e,1))),console.debug("Signed:",t.serialize()),c(e)}).catch(e=>a(e))})};y.createTx=function(e,t,n,s=null,r=null){return new Promise((i,c)=>{try{({senders:e,receivers:t,amounts:n}=u({senders:e,receivers:t,amounts:n,fee:s,change_addr:r}))}catch(e){return c(e)}let a=e.map(e=>o(e));if(a.includes(null))return c("Unable to get redeem-script");l(e,a,t,n,s,r||e[0]).then(e=>{e.tx_hex=e.transaction.serialize(),delete e.transaction,i(e)}).catch(e=>c(e))})},y.createMultiSigTx=function(e,t,n,s,r=null){return new Promise((i,o)=>{if("multisig"!==x(e))return o("Invalid sender (multisig):"+e);{let n=coinjs.script(),s=n.decodeRedeemScript(t);if(!s||s.address!==e)return o("Invalid redeem-script")}try{({receivers:n,amounts:s}=u({receivers:n,amounts:s,fee:r}))}catch(e){return o(e)}l([e],[t],n,s,r,e).then(e=>{e.tx_hex=e.transaction.serialize(),delete e.transaction,i(e)}).catch(e=>o(e))})},y.signTx=function(e,t,n=1){e=f(e),Array.isArray(t)||(t=[t]);for(let e in t)64===t[e].length&&(t[e]=coinjs.privkey2wif(t[e]));return new Set(t).forEach(t=>e.sign(t,n)),e.serialize()};const H=y.checkSigned=function(e,t=!0){e=f(e);let n=[];for(let t in e.ins){var s=e.extractScriptKey(t);if("multisig"!==s.type)n.push("true"==s.signed||e.witness[t]&&2==e.witness[t].length);else{var r=coinjs.script().decodeRedeemScript(s.script);let e={s:s.signatures,r:r.signaturesRequired,t:r.pubkeys.length};if(e.r>e.t)throw"signaturesRequired is more than publicKeys";e.s!0!==e).length:n};y.checkIfSameTx=function(e,t){if(e=f(e),t=f(t),e.ins.length!==t.ins.length||e.outs.length!==t.outs.length)return!1;for(let n=0;nnew Promise((n,s)=>{b(`get_tx_outputs/BTC/${e}/${t}`).then(e=>n(e.data.outputs)).catch(e=>s(e))});y.parseTransaction=function(e){return new Promise((t,n)=>{e=f(e);let r={},o=[];for(let t=0;t{r.inputs=n.map(e=>Object({address:e.address,value:parseFloat(e.value)}));let o=H(e,!1);r.inputs.forEach((e,t)=>e.signed=o[t]),r.outputs=e.outs.map(e=>{var t;switch(e.script.chunks[0]){case 0:t=i(Crypto.util.bytesToHex(e.script.chunks[1]),coinjs.bech32.version,coinjs.bech32.hrp);break;case 169:t=s(Crypto.util.bytesToHex(e.script.chunks[1]),coinjs.multisig);break;case 118:t=s(Crypto.util.bytesToHex(e.script.chunks[2]),coinjs.pub)}return{address:t,value:parseFloat(e.value/m)}}),r.total_input=parseFloat(r.inputs.reduce((e,t)=>e+=t.value,0).toFixed(8)),r.total_output=parseFloat(r.outputs.reduce((e,t)=>e+=t.value,0).toFixed(8)),r.fee=parseFloat((r.total_input-r.total_output).toFixed(8)),t(r)}).catch(e=>n(e))})},y.transactionID=function(e){e=f(e);let t=coinjs.clone(e);t.witness=null;let n=Crypto.util.hexToBytes(t.serialize()),s=Crypto.SHA256(Crypto.SHA256(n,{asBytes:!0}),{asBytes:!0}).reverse();return Crypto.util.bytesToHex(s)},y.getTx=(e=>new Promise((t,n)=>{b(`tx/BTC/${e}`).then(e=>t(e.data)).catch(e=>n(e))})),y.getAddressData=(e=>new Promise((t,n)=>{b(`address/BTC/${e}`).then(e=>t(e.data)).catch(e=>n(e))})),y.getBlock=(e=>new Promise((t,n)=>{b(`get_block/BTC/${e}`).then(e=>t(e.data)).catch(e=>n(e))}))})("object"==typeof module?module.exports:window.btcOperator={}); \ No newline at end of file diff --git a/scripts/compactIDB.js b/scripts/compactIDB.js index fd33d99..ba843ec 100644 --- a/scripts/compactIDB.js +++ b/scripts/compactIDB.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //compactIDB v2.1.0 +(function (EXPORTS) { //compactIDB v2.1.2 /* Compact IndexedDB operations */ 'use strict'; const compactIDB = EXPORTS; @@ -226,10 +226,9 @@ compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) { options.lowerKey = options.atKey || options.lowerKey || 0 options.upperKey = options.atKey || options.upperKey || false - options.patternEval = options.patternEval || ((k, v) => { - return true - }) + options.patternEval = options.patternEval || ((k, v) => true); options.limit = options.limit || false; + options.reverse = options.reverse || false; options.lastOnly = options.lastOnly || false return new Promise((resolve, reject) => { openDB(dbName).then(db => { @@ -237,17 +236,16 @@ var filteredResult = {} let curReq = obs.openCursor( options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey), - options.lastOnly ? "prev" : "next"); + options.lastOnly || options.reverse ? "prev" : "next"); curReq.onsuccess = (evt) => { var cursor = evt.target.result; - if (cursor) { - if (options.patternEval(cursor.primaryKey, cursor.value)) { - filteredResult[cursor.primaryKey] = cursor.value; - options.lastOnly ? resolve(filteredResult) : cursor.continue(); - } else - cursor.continue(); + if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length)) + return resolve(filteredResult); //reached end of key list or limit reached + else if (options.patternEval(cursor.primaryKey, cursor.value)) { + filteredResult[cursor.primaryKey] = cursor.value; + options.lastOnly ? resolve(filteredResult) : cursor.continue(); } else - resolve(filteredResult); + cursor.continue(); } curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); db.close(); diff --git a/scripts/compactIDB.min.js b/scripts/compactIDB.min.js deleted file mode 100644 index 15ad789..0000000 --- a/scripts/compactIDB.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){"use strict";function r(e=a){return new Promise((r,t)=>{c(e).then(e=>{r(e.version),e.close()}).catch(e=>t(e))})}function t(e,t=null,n=null){return new Promise((a,s)=>{r(e).then(r=>{var c=o.open(e,r+1);c.onerror=(e=>s("Error in opening IndexedDB")),c.onupgradeneeded=(e=>{let r=e.target.result;if(t instanceof Object){if(Array.isArray(t)){let e={};t.forEach(r=>e[r]={}),t=e}for(let e in t){let n=r.createObjectStore(e,t[e].options||{});if(t[e].indexes instanceof Object)for(let r in t[e].indexes)n.createIndex(r,r,t[e].indexes||{})}}Array.isArray(n)&&n.forEach(e=>r.deleteObjectStore(e)),a("Database upgraded")}),c.onsuccess=(e=>e.target.result.close())}).catch(e=>s(e))})}const n=e;var a;const o=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,s=(window.IDBTransaction||window.webkitIDBTransaction||window.msIDBTransaction,window.IDBKeyRange||window.webkitIDBKeyRange||window.msIDBKeyRange);if(!o)return void console.error("Your browser doesn't support a stable version of IndexedDB.");n.setDefaultDB=(e=>a=e),Object.defineProperty(n,"default",{get:()=>a,set:e=>a=e}),n.initDB=function(e,r={}){return new Promise((n,s)=>{if(!(r instanceof Object))return s("ObjectStores must be an object or array");a=a||e;var c=o.open(e);c.onerror=(e=>s("Error in opening IndexedDB")),c.onsuccess=(a=>{var o=a.target.result;let c=Object.values(o.objectStoreNames);var u={},l={},i=[];if(Array.isArray(r))r.forEach(e=>u[e]={});else u=r;let d=Object.keys(u);for(let e of d)c.includes(e)||(l[e]=u[e]);for(let e of c)d.includes(e)||i.push(e);Object.keys(l).length||i.length?t(e,l,i).then(e=>n(e)).catch(e=>s(e)):n("Initiated IndexedDB"),o.close()})})};const c=n.openDB=function(e=a){return new Promise((r,t)=>{var n=o.open(e);n.onerror=(e=>t("Error in opening IndexedDB")),n.onupgradeneeded=(r=>{r.target.result.close(),u(e).then(e=>null).catch(e=>null).finally(e=>t("Datebase not found"))}),n.onsuccess=(e=>r(e.target.result))})},u=n.deleteDB=function(e=a){return new Promise((r,t)=>{var n=o.deleteDatabase(e);n.onerror=(e=>t("Error deleting database!")),n.onsuccess=(e=>r("Database deleted successfully"))})};n.writeData=function(e,r,t=!1,n=a){return new Promise((a,o)=>{c(n).then(n=>{var s=n.transaction(e,"readwrite").objectStore(e);let c=t?s.put(r,t):s.put(r);c.onsuccess=(e=>a("Write data Successful")),c.onerror=(e=>o(`Write data unsuccessful [${e.target.error.name}] ${e.target.error.message}`)),n.close()}).catch(e=>o(e))})},n.addData=function(e,r,t=!1,n=a){return new Promise((a,o)=>{c(n).then(n=>{var s=n.transaction(e,"readwrite").objectStore(e);let c=t?s.add(r,t):s.add(r);c.onsuccess=(e=>a("Add data successful")),c.onerror=(e=>o(`Add data unsuccessful [${e.target.error.name}] ${e.target.error.message}`)),n.close()}).catch(e=>o(e))})},n.removeData=function(e,r,t=a){return new Promise((n,a)=>{c(t).then(t=>{var o=t.transaction(e,"readwrite").objectStore(e);let s=o.delete(r);s.onsuccess=(e=>n(`Removed Data ${r}`)),s.onerror=(e=>a(`Remove data unsuccessful [${e.target.error.name}] ${e.target.error.message}`)),t.close()}).catch(e=>a(e))})},n.clearData=function(e,r=a){return new Promise((t,n)=>{c(r).then(r=>{var a=r.transaction(e,"readwrite").objectStore(e);let o=a.clear();o.onsuccess=(e=>t("Clear data Successful")),o.onerror=(e=>n("Clear data Unsuccessful")),r.close()}).catch(e=>n(e))})},n.readData=function(e,r,t=a){return new Promise((n,a)=>{c(t).then(t=>{var o=t.transaction(e,"readonly").objectStore(e);let s=o.get(r);s.onsuccess=(e=>n(e.target.result)),s.onerror=(e=>a(`Read data unsuccessful [${e.target.error.name}] ${e.target.error.message}`)),t.close()}).catch(e=>a(e))})},n.readAllData=function(e,r=a){return new Promise((t,n)=>{c(r).then(r=>{var a=r.transaction(e,"readonly").objectStore(e),o={};let s=a.openCursor();s.onsuccess=(e=>{var r=e.target.result;r?(o[r.primaryKey]=r.value,r.continue()):t(o)}),s.onerror=(e=>n(`Read-All data unsuccessful [${e.target.error.name}] ${e.target.error.message}`)),r.close()}).catch(e=>n(e))})},n.searchData=function(e,r={},t=a){return r.lowerKey=r.atKey||r.lowerKey||0,r.upperKey=r.atKey||r.upperKey||!1,r.patternEval=r.patternEval||((e,r)=>!0),r.limit=r.limit||!1,r.lastOnly=r.lastOnly||!1,new Promise((n,a)=>{c(t).then(t=>{var o=t.transaction(e,"readonly").objectStore(e),c={};let u=o.openCursor(r.upperKey?s.bound(r.lowerKey,r.upperKey):s.lowerBound(r.lowerKey),r.lastOnly?"prev":"next");u.onsuccess=(e=>{var t=e.target.result;t?r.patternEval(t.primaryKey,t.value)?(c[t.primaryKey]=t.value,r.lastOnly?n(c):t.continue()):t.continue():n(c)}),u.onerror=(e=>a(`Search unsuccessful [${e.target.error.name}] ${e.target.error.message}`)),t.close()}).catch(e=>a(e))})}})(window.compactIDB={}); \ No newline at end of file diff --git a/components.js b/scripts/components.js similarity index 100% rename from components.js rename to scripts/components.js diff --git a/scripts/floBlockchainAPI.js b/scripts/floBlockchainAPI.js index 1130bb1..9dedb90 100644 --- a/scripts/floBlockchainAPI.js +++ b/scripts/floBlockchainAPI.js @@ -1,19 +1,29 @@ -(function (EXPORTS) { //floBlockchainAPI v2.3.3b - /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ +(function (EXPORTS) { //floBlockchainAPI v3.0.1b + /* FLO Blockchain Operator to send/receive data from blockchain using API calls via FLO Blockbook*/ 'use strict'; const floBlockchainAPI = EXPORTS; const DEFAULT = { blockchain: floGlobals.blockchain, apiURL: { - FLO: ['https://flosight.duckdns.org/'], - FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] + FLO: ['https://blockbook.ranchimall.net/'], + FLO_TEST: [] }, - sendAmt: 0.001, - fee: 0.0005, + sendAmt: 0.0003, + fee: 0.0002, + minChangeAmt: 0.0002, receiverID: floGlobals.adminID }; + const SATOSHI_IN_BTC = 1e8; + const isUndefined = val => typeof val === 'undefined'; + + const util = floBlockchainAPI.util = {}; + + util.Sat_to_FLO = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8)); + util.FLO_to_Sat = value => parseInt(value * SATOSHI_IN_BTC); + util.toFixed = value => parseFloat((value).toFixed(8)); + Object.defineProperties(floBlockchainAPI, { sendAmt: { get: () => DEFAULT.sendAmt, @@ -51,9 +61,9 @@ var serverList = Array.from(allServerList); var curPos = floCrypto.randInt(0, serverList.length - 1); - function fetch_retry(apicall, rm_flosight) { + function fetch_retry(apicall, rm_node) { return new Promise((resolve, reject) => { - let i = serverList.indexOf(rm_flosight) + let i = serverList.indexOf(rm_node) if (i != -1) serverList.splice(i, 1); curPos = floCrypto.randInt(0, serverList.length - 1); fetch_api(apicall, false) @@ -72,19 +82,19 @@ .then(result => resolve(result)) .catch(error => reject(error)); } else - reject("No floSight server working"); + reject("No FLO blockbook server working"); } else { - let flosight = serverList[curPos]; - fetch(flosight + apicall).then(response => { + let serverURL = serverList[curPos]; + fetch(serverURL + apicall).then(response => { if (response.ok) response.json().then(data => resolve(data)); else { - fetch_retry(apicall, flosight) + fetch_retry(apicall, serverURL) .then(result => resolve(result)) .catch(error => reject(error)); } }).catch(error => { - fetch_retry(apicall, flosight) + fetch_retry(apicall, serverURL) .then(result => resolve(result)) .catch(error => reject(error)); }) @@ -102,9 +112,11 @@ }); //Promised function to get data from API - const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) { + const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall, query_params = undefined) { return new Promise((resolve, reject) => { - //console.log(apicall); + if (!isUndefined(query_params)) + apicall += '?' + new URLSearchParams(JSON.parse(JSON.stringify(query_params))).toString(); + //console.debug(apicall); fetch_api(apicall) .then(result => resolve(result)) .catch(error => reject(error)); @@ -114,23 +126,37 @@ //Get balance for the given Address const getBalance = floBlockchainAPI.getBalance = function (addr) { return new Promise((resolve, reject) => { - promisedAPI(`api/addr/${addr}/balance`) - .then(balance => resolve(parseFloat(balance))) - .catch(error => reject(error)); + let api = `api/address/${addr}`; + promisedAPI(api, { details: "basic" }) + .then(result => resolve(result["balance"])) + .catch(error => reject(error)) }); } - //Send Tx to blockchain - const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { + function getScriptPubKey(address) { + var tx = bitjs.transaction(); + tx.addoutput(address, 0); + let outputBuffer = tx.outputs.pop().script; + return Crypto.util.bytesToHex(outputBuffer) + } + + const getUTXOs = address => new Promise((resolve, reject) => { + promisedAPI(`api/utxo/${address}`, { confirmed: true }).then(utxos => { + let scriptPubKey = getScriptPubKey(address); + utxos.forEach(u => u.scriptPubKey = scriptPubKey); + resolve(utxos); + }).catch(error => reject(error)) + }) + + //create a transaction with single sender + const createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); - else if (!floCrypto.validateFloID(senderAddr)) + else if (!floCrypto.validateFloID(senderAddr, true)) return reject(`Invalid address : ${senderAddr}`); else if (!floCrypto.validateFloID(receiverAddr)) return reject(`Invalid address : ${receiverAddr}`); - else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr)) - return reject("Invalid Private key!"); else if (typeof sendAmt !== 'number' || sendAmt <= 0) return reject(`Invalid sendAmt : ${sendAmt}`); @@ -138,51 +164,54 @@ var fee = DEFAULT.fee; if (balance < sendAmt + fee) return reject("Insufficient FLO balance!"); - //get unconfirmed tx list - promisedAPI(`api/addr/${senderAddr}`).then(result => { - readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => { - let unconfirmedSpent = {}; - for (let tx of result.items) - if (tx.confirmations == 0) - for (let vin of tx.vin) - if (vin.addr === senderAddr) { - if (Array.isArray(unconfirmedSpent[vin.txid])) - unconfirmedSpent[vin.txid].push(vin.vout); - else - unconfirmedSpent[vin.txid] = [vin.vout]; - } - //get utxos list - promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => { - //form/construct the transaction data - var trx = bitjs.transaction(); - var utxoAmt = 0.0; - for (var i = utxos.length - 1; - (i >= 0) && (utxoAmt < sendAmt + fee); i--) { - //use only utxos with confirmations (strict_utxo mode) - if (utxos[i].confirmations || !strict_utxo) { - if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout)) - continue; //A transaction has already used the utxo, but is unconfirmed. - trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); - utxoAmt += utxos[i].amount; - }; - } - if (utxoAmt < sendAmt + fee) - reject("Insufficient FLO: Some UTXOs are unconfirmed"); - else { - trx.addoutput(receiverAddr, sendAmt); - var change = utxoAmt - sendAmt - fee; - if (change > 0) - trx.addoutput(senderAddr, change); - trx.addflodata(floData.replace(/\n/g, ' ')); - var signedTxHash = trx.sign(privKey, 1); - broadcastTx(signedTxHash) - .then(txid => resolve(txid)) - .catch(error => reject(error)) - } - }).catch(error => reject(error)) - }).catch(error => reject(error)) + getUTXOs(senderAddr).then(utxos => { + //form/construct the transaction data + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + for (var i = utxos.length - 1; + (i >= 0) && (utxoAmt < sendAmt + fee); i--) { + //use only utxos with confirmations (strict_utxo mode) + if (utxos[i].confirmations || !strict_utxo) { + trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); + utxoAmt += utxos[i].amount; + }; + } + if (utxoAmt < sendAmt + fee) + reject("Insufficient FLO: Some UTXOs are unconfirmed"); + else { + trx.addoutput(receiverAddr, sendAmt); + var change = utxoAmt - sendAmt - fee; + if (change > DEFAULT.minChangeAmt) + trx.addoutput(senderAddr, change); + trx.addflodata(floData.replace(/\n/g, ' ')); + resolve(trx); + } }).catch(error => reject(error)) }).catch(error => reject(error)) + }) + } + + floBlockchainAPI.createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo) + .then(trx => resolve(trx.serialize())) + .catch(error => reject(error)) + }) + } + + //Send Tx to blockchain + const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + if (!floCrypto.validateFloID(senderAddr, true)) + return reject(`Invalid address : ${senderAddr}`); + else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr)) + return reject("Invalid Private key!"); + createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo).then(trx => { + var signedTxHash = trx.sign(privKey, 1); + broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }).catch(error => reject(error)) }); } @@ -202,7 +231,7 @@ //merge all UTXOs of a given floID into a single UTXO floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') { return new Promise((resolve, reject) => { - if (!floCrypto.validateFloID(floID)) + if (!floCrypto.validateFloID(floID, true)) return reject(`Invalid floID`); if (!floCrypto.verifyPrivKey(privKey, floID)) return reject("Invalid Private Key"); @@ -211,7 +240,7 @@ var trx = bitjs.transaction(); var utxoAmt = 0.0; var fee = DEFAULT.fee; - promisedAPI(`api/addr/${floID}/utxo`).then(utxos => { + getUTXOs(floID).then(utxos => { for (var i = utxos.length - 1; i >= 0; i--) if (utxos[i].confirmations) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); @@ -227,6 +256,52 @@ }) } + //split sufficient UTXOs of a given floID for a parallel sending + floBlockchainAPI.splitUTXOs = function (floID, privKey, count, floData = '') { + return new Promise((resolve, reject) => { + if (!floCrypto.validateFloID(floID, true)) + return reject(`Invalid floID`); + if (!floCrypto.verifyPrivKey(privKey, floID)) + return reject("Invalid Private Key"); + if (!floCrypto.validateASCII(floData)) + return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); + var fee = DEFAULT.fee; + var splitAmt = DEFAULT.sendAmt + fee; + var totalAmt = splitAmt * count; + getBalance(floID).then(balance => { + var fee = DEFAULT.fee; + if (balance < totalAmt + fee) + return reject("Insufficient FLO balance!"); + //get unconfirmed tx list + getUTXOs(floID).then(utxos => { + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) { + //use only utxos with confirmations (strict_utxo mode) + if (utxos[i].confirmations || !strict_utxo) { + trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); + utxoAmt += utxos[i].amount; + }; + } + if (utxoAmt < totalAmt + fee) + reject("Insufficient FLO: Some UTXOs are unconfirmed"); + else { + for (let i = 0; i < count; i++) + trx.addoutput(floID, splitAmt); + var change = utxoAmt - totalAmt - fee; + if (change > DEFAULT.minChangeAmt) + trx.addoutput(floID, change); + trx.addflodata(floData.replace(/\n/g, ' ')); + var signedTxHash = trx.sign(privKey, 1); + broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + } + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + /**Write data into blockchain from (and/or) to multiple floID * @param {Array} senderPrivKeys List of sender private-keys * @param {string} data FLO data of the txn @@ -234,11 +309,11 @@ * @param {boolean} preserveRatio (optional) preserve ratio or equal contribution * @return {Promise} */ - floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) { + floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], options = {}) { return new Promise((resolve, reject) => { if (!Array.isArray(senderPrivKeys)) return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array"); - if (!preserveRatio) { + if (options.preserveRatio === false) { let tmp = {}; let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length; senderPrivKeys.forEach(key => tmp[key] = amount); @@ -248,7 +323,7 @@ return reject("Invalid receivers: Receivers must be Array"); else { let tmp = {}; - let amount = DEFAULT.sendAmt; + let amount = options.sendAmt || DEFAULT.sendAmt; receivers.forEach(floID => tmp[floID] = amount); receivers = tmp } @@ -378,9 +453,8 @@ //Get the UTXOs of the senders let promises = []; for (let floID in senders) - promises.push(promisedAPI(`api/addr/${floID}/utxo`)); + promises.push(getUTXOs(floID)); Promise.all(promises).then(results => { - let wifSeq = []; var trx = bitjs.transaction(); for (let floID in senders) { let utxos = results.shift(); @@ -390,13 +464,11 @@ sendAmt = totalSendAmt * ratio; } else sendAmt = senders[floID].coins + dividedFee; - let wif = senders[floID].wif; let utxoAmt = 0.0; for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < sendAmt); i--) { if (utxos[i].confirmations) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); - wifSeq.push(wif); utxoAmt += utxos[i].amount; } } @@ -409,8 +481,8 @@ for (let floID in receivers) trx.addoutput(floID, receivers[floID]); trx.addflodata(floData.replace(/\n/g, ' ')); - for (let i = 0; i < wifSeq.length; i++) - trx.signinput(i, wifSeq[i], 1); + for (let floID in senders) + trx.sign(senders[floID].wif, 1); var signedTxHash = trx.serialize(); broadcastTx(signedTxHash) .then(txid => resolve(txid)) @@ -420,28 +492,268 @@ }) } + //Create a multisig transaction + const createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + var multisig = floCrypto.decodeRedeemScript(redeemScript); + + //validate multisig script and flodata + if (!multisig) + return reject(`Invalid redeemScript`); + var senderAddr = multisig.address; + if (!floCrypto.validateFloID(senderAddr)) + return reject(`Invalid multisig : ${senderAddr}`); + else if (!floCrypto.validateASCII(floData)) + return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); + //validate receiver addresses + if (!Array.isArray(receivers)) + receivers = [receivers]; + for (let r of receivers) + if (!floCrypto.validateFloID(r)) + return reject(`Invalid address : ${r}`); + //validate amounts + if (!Array.isArray(amounts)) + amounts = [amounts]; + if (amounts.length != receivers.length) + return reject("Receivers and amounts have different length"); + var sendAmt = 0; + for (let a of amounts) { + if (typeof a !== 'number' || a <= 0) + return reject(`Invalid amount : ${a}`); + sendAmt += a; + } + + getBalance(senderAddr).then(balance => { + var fee = DEFAULT.fee; + if (balance < sendAmt + fee) + return reject("Insufficient FLO balance!"); + getUTXOs(senderAddr).then(utxos => { + //form/construct the transaction data + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + for (var i = utxos.length - 1; + (i >= 0) && (utxoAmt < sendAmt + fee); i--) { + //use only utxos with confirmations (strict_utxo mode) + if (utxos[i].confirmations || !strict_utxo) { + trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript + utxoAmt += utxos[i].amount; + }; + } + if (utxoAmt < sendAmt + fee) + reject("Insufficient FLO: Some UTXOs are unconfirmed"); + else { + for (let i in receivers) + trx.addoutput(receivers[i], amounts[i]); + var change = utxoAmt - sendAmt - fee; + if (change > DEFAULT.minChangeAmt) + trx.addoutput(senderAddr, change); + trx.addflodata(floData.replace(/\n/g, ' ')); + resolve(trx); + } + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }); + } + + //Same as above, but explict call should return serialized tx-hex + floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo) + .then(trx => resolve(trx.serialize())) + .catch(error => reject(error)) + }) + } + + //Create and send multisig transaction + const sendMultisigTx = floBlockchainAPI.sendMultisigTx = function (redeemScript, privateKeys, receivers, amounts, floData = '', strict_utxo = true) { + return new Promise((resolve, reject) => { + var multisig = floCrypto.decodeRedeemScript(redeemScript); + if (!multisig) + return reject(`Invalid redeemScript`); + if (privateKeys.length < multisig.required) + return reject(`Insufficient privateKeys (required ${multisig.required})`); + for (let pk of privateKeys) { + var flag = false; + for (let pub of multisig.pubkeys) + if (floCrypto.verifyPrivKey(pk, pub, false)) + flag = true; + if (!flag) + return reject(`Invalid Private key`); + } + createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo).then(trx => { + for (let pk of privateKeys) + trx.sign(pk, 1); + var signedTxHash = trx.serialize(); + broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + + floBlockchainAPI.writeMultisigData = function (redeemScript, data, privatekeys, receiverAddr = DEFAULT.receiverID, options = {}) { + let strict_utxo = options.strict_utxo === false ? false : true, + sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt; + return new Promise((resolve, reject) => { + if (!floCrypto.validateFloID(receiverAddr)) + return reject(`Invalid receiver: ${receiverAddr}`); + sendMultisigTx(redeemScript, privatekeys, receiverAddr, sendAmt, data, strict_utxo) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }) + } + + function deserializeTx(tx) { + if (typeof tx === 'string' || Array.isArray(tx)) { + try { + tx = bitjs.transaction(tx); + } catch { + throw "Invalid transaction hex"; + } + } else if (typeof tx !== 'object' || typeof tx.sign !== 'function') + throw "Invalid transaction object"; + return tx; + } + + floBlockchainAPI.signTx = function (tx, privateKey, sighashtype = 1) { + if (!floCrypto.getFloID(privateKey)) + throw "Invalid Private key"; + //deserialize if needed + tx = deserializeTx(tx); + var signedTxHex = tx.sign(privateKey, sighashtype); + return signedTxHex; + } + + const checkSigned = floBlockchainAPI.checkSigned = function (tx, bool = true) { + tx = deserializeTx(tx); + let n = []; + for (let i = 0; i < tx.inputs.length; i++) { + var s = tx.scriptDecode(i); + if (s['type'] === 'scriptpubkey') + n.push(s.signed); + else if (s['type'] === 'multisig') { + var rs = tx.decodeRedeemScript(s['rs']); + let x = { + s: 0, + r: rs['required'], + t: rs['pubkeys'].length + }; + //check input script for signatures + var script = Array.from(tx.inputs[i].script); + if (script[0] == 0) { //script with signatures + script = tx.parseScript(script); + for (var k = 0; k < script.length; k++) + if (Array.isArray(script[k]) && script[k][0] == 48) //0x30 DERSequence + x.s++; + } + //validate counts + 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; + } + + floBlockchainAPI.checkIfSameTx = function (tx1, tx2) { + tx1 = deserializeTx(tx1); + tx2 = deserializeTx(tx2); + //compare input and output length + if (tx1.inputs.length !== tx2.inputs.length || tx1.outputs.length !== tx2.outputs.length) + return false; + //compare flodata + if (tx1.floData !== tx2.floData) + return false + //compare inputs + for (let i = 0; i < tx1.inputs.length; i++) + if (tx1.inputs[i].outpoint.hash !== tx2.inputs[i].outpoint.hash || tx1.inputs[i].outpoint.index !== tx2.inputs[i].outpoint.index) + return false; + //compare outputs + for (let i = 0; i < tx1.outputs.length; i++) + if (tx1.outputs[i].value !== tx2.outputs[i].value || Crypto.util.bytesToHex(tx1.outputs[i].script) !== Crypto.util.bytesToHex(tx2.outputs[i].script)) + return false; + return true; + } + + floBlockchainAPI.transactionID = function (tx) { + tx = deserializeTx(tx); + let clone = bitjs.clone(tx); + 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 getTxOutput = (txid, i) => new Promise((resolve, reject) => { + promisedAPI(`api/tx/${txid}`) + .then(result => resolve(result.vout[i])) + .catch(error => reject(error)) + }); + + function getOutputAddress(outscript) { + var bytes, version; + switch (outscript[0]) { + case 118: //legacy + bytes = outscript.slice(3, outscript.length - 2); + version = bitjs.pub; + break + case 169: //multisig + bytes = outscript.slice(2, outscript.length - 1); + version = bitjs.multisig; + break; + default: return; //unknown + } + bytes.unshift(version); + var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), { asBytes: true }); + var checksum = hash.slice(0, 4); + return bitjs.Base58.encode(bytes.concat(checksum)); + } + + floBlockchainAPI.parseTransaction = function (tx) { + return new Promise((resolve, reject) => { + tx = deserializeTx(tx); + let result = {}; + let promises = []; + //Parse Inputs + for (let i = 0; i < tx.inputs.length; i++) + promises.push(getTxOutput(tx.inputs[i].outpoint.hash, tx.inputs[i].outpoint.index)); + Promise.all(promises).then(inputs => { + result.inputs = inputs.map(inp => Object({ + address: inp.scriptPubKey.addresses[0], + value: parseFloat(inp.value) + })); + let signed = checkSigned(tx, false); + result.inputs.forEach((inp, i) => inp.signed = signed[i]); + //Parse Outputs + result.outputs = tx.outputs.map(out => Object({ + address: getOutputAddress(out.script), + value: util.Sat_to_FLO(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)); + result.floData = tx.floData; + resolve(result); + }).catch(error => reject(error)) + }) + } + //Broadcast signed Tx in blockchain using API const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { return new Promise((resolve, reject) => { if (signedTxHash.length < 1) - return reject("Empty Signature"); - var url = serverList[curPos] + 'api/tx/send'; - fetch(url, { - method: "POST", - headers: { - 'Content-Type': 'application/json' - }, - body: `{"rawtx":"${signedTxHash}"}` - }).then(response => { - if (response.ok) - response.json().then(data => resolve(data.txid.result)); - else - response.text().then(data => resolve(data)); - }).catch(error => reject(error)); + return reject("Empty Transaction Data"); + + promisedAPI('/api/sendtx/' + signedTxHash) + .then(response => resolve(response["result"])) + .catch(error => reject(error)) }) } - floBlockchainAPI.getTx = function (txid) { + const getTx = floBlockchainAPI.getTx = function (txid) { return new Promise((resolve, reject) => { promisedAPI(`api/tx/${txid}`) .then(response => resolve(response)) @@ -449,30 +761,124 @@ }) } - //Read Txs of Address between from and to - const readTxs = floBlockchainAPI.readTxs = function (addr, from, to) { + /**Wait for the given txid to get confirmation in blockchain + * @param {string} txid of the transaction to wait for + * @param {int} max_retry: maximum number of retries before exiting wait. negative number = Infinite retries (DEFAULT: -1 ie, infinite retries) + * @param {Array} retry_timeout: time (seconds) between retries (DEFAULT: 20 seconds) + * @return {Promise} resolves when tx gets confirmation + */ + const waitForConfirmation = floBlockchainAPI.waitForConfirmation = function (txid, max_retry = -1, retry_timeout = 20) { return new Promise((resolve, reject) => { - promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }); + setTimeout(function () { + getTx(txid).then(tx => { + if (!tx) + return reject("Transaction not found"); + if (tx.confirmations) + return resolve(tx); + else if (max_retry === 0) //no more retries + return reject("Waiting timeout: tx still not confirmed"); + else { + max_retry = max_retry < 0 ? -1 : max_retry - 1; //decrease retry count (unless infinite retries) + waitForConfirmation(txid, max_retry, retry_timeout) + .then(result => resolve(result)) + .catch(error => reject(error)) + } + }).catch(error => reject(error)) + }, retry_timeout * 1000) + }) } - //Read All Txs of Address (newest first) - floBlockchainAPI.readAllTxs = function (addr) { + //Read Txs of Address + const readTxs = floBlockchainAPI.readTxs = function (addr, options = {}) { return new Promise((resolve, reject) => { - promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { - promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`) - .then(response => resolve(response.items)) - .catch(error => reject(error)); + //API options + let query_params = { details: 'txs' }; + //page options + if (!isUndefined(options.page) && Number.isInteger(options.page)) + query_params.page = options.page; + if (!isUndefined(options.pageSize) && Number.isInteger(options.pageSize)) + query_params.pageSize = options.pageSize; + //only confirmed tx + if (options.confirmed) //Default is false in server, so only add confirmed filter if confirmed has a true value + query_params.confirmed = true; + + promisedAPI(`api/address/${addr}`, query_params).then(response => { + if (!Array.isArray(response.txs)) //set empty array if address doesnt have any tx + response.txs = []; + resolve(response) }).catch(error => reject(error)) }); } + //backward support (floBlockchainAPI < v2.5.6) + function readAllTxs_oldSupport(addr, options, ignoreOld = 0, cacheTotal = 0) { + return new Promise((resolve, reject) => { + readTxs(addr, options).then(response => { + cacheTotal += response.txs.length; + let n_remaining = response.txApperances - cacheTotal + if (n_remaining < ignoreOld) { // must remove tx that would have been fetch during prev call + let n_remove = ignoreOld - n_remaining; + resolve(response.txs.slice(0, -n_remove)); + } else if (response.page == response.totalPages) //last page reached + resolve(response.txs); + else { + options.page = response.page + 1; + readAllTxs_oldSupport(addr, options, ignoreOld, cacheTotal) + .then(result => resolve(response.txs.concat(result))) + .catch(error => reject(error)) + } + }).catch(error => reject(error)) + }) + } + + function readAllTxs_new(addr, options, lastItem) { + return new Promise((resolve, reject) => { + readTxs(addr, options).then(response => { + let i = response.txs.findIndex(t => t.txid === lastItem); + if (i != -1) //found lastItem + resolve(response.txs.slice(0, i)) + else if (response.page == response.totalPages) //last page reached + resolve(response.txs); + else { + options.page = response.page + 1; + readAllTxs_new(addr, options, lastItem) + .then(result => resolve(response.txs.concat(result))) + .catch(error => reject(error)) + } + }).catch(error => reject(error)) + }) + } + + //Read All Txs of Address (newest first) + const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) { + return new Promise((resolve, reject) => { + if (Number.isInteger(options.ignoreOld)) //backward support: data from floBlockchainAPI < v2.5.6 + readAllTxs_oldSupport(addr, options, options.ignoreOld).then(txs => { + let last_tx = txs.find(t => t.confirmations > 0); + let new_lastItem = last_tx ? last_tx.txid : options.ignoreOld; + resolve({ + lastItem: new_lastItem, + items: txs + }) + + }).catch(error => reject(error)) + else //New format for floBlockchainAPI >= v2.5.6 + readAllTxs_new(addr, options, options.after).then(txs => { + let last_tx = txs.find(t => t.confirmations > 0); + let new_lastItem = last_tx ? last_tx.txid : options.after; + resolve({ + lastItem: new_lastItem, + items: txs + }) + }).catch(error => reject(error)) + }) + } + /*Read flo Data from txs of given Address options can be used to filter data - limit : maximum number of filtered data (default = 1000, negative = no limit) - ignoreOld : ignore old txs (default = 0) + after : query after the given txid + confirmed : query only confirmed tx or not (options same as readAllTx, DEFAULT=true: only_confirmed_tx) + ignoreOld : ignore old txs (deprecated: support for backward compatibility only, cannot be used with 'after') sentOnly : filters only sent data receivedOnly: filters only received data pattern : filters data that with JSON pattern @@ -482,96 +888,157 @@ receiver : flo-id(s) of receiver */ floBlockchainAPI.readData = function (addr, options = {}) { - options.limit = options.limit || 0; - options.ignoreOld = options.ignoreOld || 0; - if (typeof options.sender === "string") options.sender = [options.sender]; - if (typeof options.receiver === "string") options.receiver = [options.receiver]; return new Promise((resolve, reject) => { - promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { - var newItems = response.totalItems - options.ignoreOld; - promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems * 2}`).then(response => { - if (options.limit <= 0) - options.limit = response.items.length; - var filteredData = []; - let numToRead = response.totalItems - options.ignoreOld, - unconfirmedCount = 0; - for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) { - if (!response.items[i].confirmations) { //unconfirmed transactions - unconfirmedCount++; - if (numToRead < response.items[i].length) - numToRead++; - continue; - } - if (options.pattern) { - try { - let jsonContent = JSON.parse(response.items[i].floData); - if (!Object.keys(jsonContent).includes(options.pattern)) - continue; - } catch (error) { - continue; - } - } - if (options.sentOnly) { - let flag = false; - for (let vin of response.items[i].vin) - if (vin.addr === addr) { - flag = true; - break; - } - if (!flag) continue; - } - if (Array.isArray(options.sender)) { - let flag = false; - for (let vin of response.items[i].vin) - if (options.sender.includes(vin.addr)) { - flag = true; - break; - } - if (!flag) continue; - } - if (options.receivedOnly) { - let flag = false; - for (let vout of response.items[i].vout) - if (vout.scriptPubKey.addresses[0] === addr) { - flag = true; - break; - } - if (!flag) continue; - } - if (Array.isArray(options.receiver)) { - let flag = false; - for (let vout of response.items[i].vout) - if (options.receiver.includes(vout.scriptPubKey.addresses[0])) { - flag = true; - break; - } - if (!flag) continue; - } - if (options.filter && !options.filter(response.items[i].floData)) - continue; - if (options.tx) { - let d = {} - d.txid = response.items[i].txid; - d.time = response.items[i].time; - d.blockheight = response.items[i].blockheight; - d.data = response.items[i].floData; - filteredData.push(d); - } else - filteredData.push(response.items[i].floData); + //fetch options + let query_options = {}; + query_options.confirmed = isUndefined(options.confirmed) ? true : options.confirmed; //DEFAULT: ignore unconfirmed tx + + if (!isUndefined(options.after)) + query_options.after = options.after; + else if (!isUndefined(options.ignoreOld)) + query_options.ignoreOld = options.ignoreOld; + + readAllTxs(addr, query_options).then(response => { + + if (typeof options.senders === "string") options.senders = [options.senders]; + if (typeof options.receivers === "string") options.receivers = [options.receivers]; + + //filter the txs based on options + const filteredData = response.items.filter(tx => { + + if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query + return false; + + if (options.sentOnly && !tx.vin.some(vin => vin.addresses[0] === addr)) + return false; + else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addresses[0]))) + return false; + + if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr)) + return false; + else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0]))) + return false; + + if (options.pattern) { + try { + let jsonContent = JSON.parse(tx.floData); + if (!Object.keys(jsonContent).includes(options.pattern)) + return false; + } catch { + return false; + } } - resolve({ - totalTxs: response.totalItems - unconfirmedCount, - data: filteredData - }); - }).catch(error => { - reject(error); - }); - }).catch(error => { - reject(error); - }); - }); + + if (options.filter && !options.filter(tx.floData)) + return false; + + return true; + }).map(tx => options.tx ? { + txid: tx.txid, + time: tx.time, + blockheight: tx.blockheight, + senders: new Set(tx.vin.map(v => v.addresses[0])), + receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])), + data: tx.floData + } : tx.floData); + + const result = { lastItem: response.lastItem }; + if (options.tx) + result.items = filteredData; + else + result.data = filteredData + resolve(result); + + }).catch(error => reject(error)) + }) } + /*Get the latest flo Data that match the caseFn from txs of given Address + caseFn: (function) flodata => return bool value + options can be used to filter data + after : query after the given txid + confirmed : query only confirmed tx or not (options same as readAllTx, DEFAULT=true: only_confirmed_tx) + sentOnly : filters only sent data + receivedOnly: filters only received data + tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details) + sender : flo-id(s) of sender + receiver : flo-id(s) of receiver + */ + const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) { + return new Promise((resolve, reject) => { + //fetch options + let query_options = {}; + query_options.confirmed = isUndefined(options.confirmed) ? true : options.confirmed; //DEFAULT: confirmed tx only + if (!isUndefined(options.page)) + query_options.page = options.page; + //if (!isUndefined(options.after)) query_options.after = options.after; + + let new_lastItem; + readTxs(addr, query_options).then(response => { + + //lastItem confirmed tx checked + if (!new_lastItem) { + let last_tx = response.items.find(t => t.confirmations > 0); + if (last_tx) + new_lastItem = last_tx.txid; + } + + if (typeof options.senders === "string") options.senders = [options.senders]; + if (typeof options.receivers === "string") options.receivers = [options.receivers]; + + //check if `after` txid is in the response + let i_after = response.txs.findIndex(t => t.txid === options.after); + if (i_after != -1) //found lastItem, hence remove it and all txs before that + response.items.splice(i_after); + + var item = response.items.find(tx => { + if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query + return false; + + if (options.sentOnly && !tx.vin.some(vin => vin.addresses[0] === addr)) + return false; + else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addresses[0]))) + return false; + + if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr)) + return false; + else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0]))) + return false; + + return caseFn(tx.floData) ? true : false; //return only bool for find fn + }); + + //if item found, then resolve the result + if (!isUndefined(item)) { + const result = { lastItem: new_lastItem || item.txid }; + if (options.tx) { + result.item = { + txid: item.txid, + time: item.time, + blockheight: item.blockheight, + senders: new Set(item.vin.map(v => v.addresses[0])), + receivers: new Set(item.vout.map(v => v.scriptPubKey.addresses[0])), + data: item.floData + } + } else + result.data = item.floData; + return resolve(result); + } + + if (response.page == response.totalPages || i_after != -1) //reached last page to check + resolve({ lastItem: new_lastItem || options.after }); //no data match the caseFn, resolve just the lastItem + + //else if address needs chain query + else { + options.page = response.page + 1; + getLatestData(addr, caseFn, options) + .then(result => resolve(result)) + .catch(error => reject(error)) + } + + }).catch(error => reject(error)) + }) + } })('object' === typeof module ? module.exports : window.floBlockchainAPI = {}); \ No newline at end of file diff --git a/scripts/floBlockchainAPI.min.js b/scripts/floBlockchainAPI.min.js deleted file mode 100644 index a2eff1b..0000000 --- a/scripts/floBlockchainAPI.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){"use strict";function t(e,t){return new Promise((n,i)=>{let a=o.indexOf(t);-1!=a&&o.splice(a,1),l=floCrypto.randInt(0,o.length-1),r(e,!1).then(e=>n(e)).catch(e=>i(e))})}function r(e,n=!0){return new Promise((i,s)=>{if(0===o.length)n?(o=Array.from(a),l=floCrypto.randInt(0,o.length-1),r(e,!1).then(e=>i(e)).catch(e=>s(e))):s("No floSight server working");else{let r=o[l];fetch(r+e).then(n=>{n.ok?n.json().then(e=>i(e)):t(e,r).then(e=>i(e)).catch(e=>s(e))}).catch(n=>{t(e,r).then(e=>i(e)).catch(e=>s(e))})}})}const n=e,i={blockchain:floGlobals.blockchain,apiURL:{FLO:["https://flosight.duckdns.org/"],FLO_TEST:["https://testnet-flosight.duckdns.org","https://testnet.flocha.in/"]},sendAmt:.001,fee:5e-4,receiverID:floGlobals.adminID};Object.defineProperties(n,{sendAmt:{get:()=>i.sendAmt,set:e=>isNaN(e)?null:i.sendAmt=e},fee:{get:()=>i.fee,set:e=>isNaN(e)?null:i.fee=e},defaultReceiver:{get:()=>i.receiverID,set:e=>i.receiverID=e},blockchain:{get:()=>i.blockchain}}),floGlobals.sendAmt&&(n.sendAmt=floGlobals.sendAmt),floGlobals.fee&&(n.fee=floGlobals.fee),Object.defineProperties(floGlobals,{sendAmt:{get:()=>i.sendAmt,set:e=>isNaN(e)?null:i.sendAmt=e},fee:{get:()=>i.fee,set:e=>isNaN(e)?null:i.fee=e}});const a=new Set(floGlobals.apiURL&&floGlobals.apiURL[i.blockchain]?floGlobals.apiURL[i.blockchain]:i.apiURL[i.blockchain]);var o=Array.from(a),l=floCrypto.randInt(0,o.length-1);Object.defineProperties(n,{serverList:{get:()=>Array.from(o)},current_server:{get:()=>o[l]}});const s=n.promisedAPI=n.fetch=function(e){return new Promise((t,n)=>{r(e).then(e=>t(e)).catch(e=>n(e))})},c=n.getBalance=function(e){return new Promise((t,r)=>{s(`api/addr/${e}/balance`).then(e=>t(parseFloat(e))).catch(e=>r(e))})},f=n.sendTx=function(e,t,r,n,a="",o=!0){return new Promise((l,f)=>floCrypto.validateASCII(a)?floCrypto.validateFloID(e)?floCrypto.validateFloID(t)?n.length<1||!floCrypto.verifyPrivKey(n,e)?f("Invalid Private key!"):"number"!=typeof r||r<=0?f(`Invalid sendAmt : ${r}`):void c(e).then(c=>{var d=i.fee;if(c{h(e,0,i.unconfirmedTxApperances).then(i=>{let c={};for(let t of i.items)if(0==t.confirmations)for(let r of t.vin)r.addr===e&&(Array.isArray(c[r.txid])?c[r.txid].push(r.vout):c[r.txid]=[r.vout]);s(`api/addr/${e}/utxo`).then(i=>{for(var s=bitjs.transaction(),h=0,p=i.length-1;p>=0&&h0&&s.addoutput(e,m),s.addflodata(a.replace(/\n/g," "));var v=s.sign(n,1);u(v).then(e=>l(e)).catch(e=>f(e))}}).catch(e=>f(e))}).catch(e=>f(e))}).catch(e=>f(e))}).catch(e=>f(e)):f(`Invalid address : ${t}`):f(`Invalid address : ${e}`):f("Invalid FLO_Data: only printable ASCII characters are allowed"))};n.writeData=function(e,t,r,n=i.receiverID,a={}){let o=!1!==a.strict_utxo,l=isNaN(a.sendAmt)?i.sendAmt:a.sendAmt;return new Promise((i,a)=>{"string"!=typeof t&&(t=JSON.stringify(t)),f(e,n,l,r,t,o).then(e=>i(e)).catch(e=>a(e))})},n.mergeUTXOs=function(e,t,r=""){return new Promise((n,a)=>{if(!floCrypto.validateFloID(e))return a("Invalid floID");if(!floCrypto.verifyPrivKey(t,e))return a("Invalid Private Key");if(!floCrypto.validateASCII(r))return a("Invalid FLO_Data: only printable ASCII characters are allowed");var o=bitjs.transaction(),l=0,c=i.fee;s(`api/addr/${e}/utxo`).then(i=>{for(var s=i.length-1;s>=0;s--)i[s].confirmations&&(o.addinput(i[s].txid,i[s].vout,i[s].scriptPubKey),l+=i[s].amount);o.addoutput(e,l-c),o.addflodata(r.replace(/\n/g," "));var f=o.sign(t,1);u(f).then(e=>n(e)).catch(e=>a(e))}).catch(e=>a(e))})},n.writeDataMultiple=function(e,t,r=[i.receiverID],n=!0){return new Promise((a,o)=>{if(!Array.isArray(e))return o("Invalid senderPrivKeys: SenderPrivKeys must be Array");if(!n){let t={},n=i.sendAmt*r.length/e.length;e.forEach(e=>t[e]=n),e=t}if(!Array.isArray(r))return o("Invalid receivers: Receivers must be Array");{let e={},t=i.sendAmt;r.forEach(r=>e[r]=t),r=e}"string"!=typeof t&&(t=JSON.stringify(t)),d(e,r,t).then(e=>a(e)).catch(e=>o(e))})};const d=n.sendTxMultiple=function(e,t,r=""){return new Promise((n,a)=>{if(!floCrypto.validateASCII(r))return a("Invalid FLO_Data: only printable ASCII characters are allowed");let o,l={};try{let r={InvalidSenderPrivKeys:[],InvalidSenderAmountFor:[],InvalidReceiverIDs:[],InvalidReceiveAmountFor:[]},n=0,i=0;if(Array.isArray(e))e.forEach(e=>{try{if(e){let t=floCrypto.getFloID(e);l[t]={wif:e}}else r.InvalidSenderPrivKeys.push(e)}catch(t){r.InvalidSenderPrivKeys.push(e)}}),o=!0;else{for(let t in e)try{if(t){"number"!=typeof e[t]||e[t]<=0?r.InvalidSenderAmountFor.push(t):n+=e[t];let i=floCrypto.getFloID(t);l[i]={wif:t,coins:e[t]}}else r.InvalidSenderPrivKeys.push(t)}catch(e){r.InvalidSenderPrivKeys.push(t)}o=!1}for(let e in t)floCrypto.validateFloID(e)||r.InvalidReceiverIDs.push(e),"number"!=typeof t[e]||t[e]<=0?r.InvalidReceiveAmountFor.push(e):i+=t[e];for(let e in r)r[e].length||delete r[e];if(Object.keys(r).length)return a(r);if(!o&&n!=i)return a(`Input Amount (${n}) not equal to Output Amount (${i})`)}catch(e){return a(e)}let f=[];for(let e in l)f.push(c(e));Promise.all(f).then(e=>{let c=0,f=i.fee,d={};if(!o)var h=f/Object.keys(l).length;let p=[];for(let t in l)d[t]=parseFloat(e.shift()),(isNaN(d[t])||o&&d[t]<=f||!o&&d[t]{let i=[];var s=bitjs.transaction();for(let t in l){let r,n=e.shift();if(o){let e=d[t]/c;r=m*e}else r=l[t].coins+h;let f=l[t].wif,u=0;for(let e=n.length-1;e>=0&&u0&&s.addoutput(t,p)}for(let e in t)s.addoutput(e,t[e]);s.addflodata(r.replace(/\n/g," "));for(let e=0;en(e)).catch(e=>a(e))}).catch(e=>a(e))}).catch(e=>a(e))})},u=n.broadcastTx=function(e){return new Promise((t,r)=>{if(e.length<1)return r("Empty Signature");var n=o[l]+"api/tx/send";fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:`{"rawtx":"${e}"}`}).then(e=>{e.ok?e.json().then(e=>t(e.txid.result)):e.text().then(e=>t(e))}).catch(e=>r(e))})};n.getTx=function(e){return new Promise((t,r)=>{s(`api/tx/${e}`).then(e=>t(e)).catch(e=>r(e))})};const h=n.readTxs=function(e,t,r){return new Promise((n,i)=>{s(`api/addrs/${e}/txs?from=${t}&to=${r}`).then(e=>n(e)).catch(e=>i(e))})};n.readAllTxs=function(e){return new Promise((t,r)=>{s(`api/addrs/${e}/txs?from=0&to=1`).then(n=>{s(`api/addrs/${e}/txs?from=0&to=${n.totalItems}0`).then(e=>t(e.items)).catch(e=>r(e))}).catch(e=>r(e))})},n.readData=function(e,t={}){return t.limit=t.limit||0,t.ignoreOld=t.ignoreOld||0,"string"==typeof t.sender&&(t.sender=[t.sender]),"string"==typeof t.receiver&&(t.receiver=[t.receiver]),new Promise((r,n)=>{s(`api/addrs/${e}/txs?from=0&to=1`).then(i=>{var a=i.totalItems-t.ignoreOld;s(`api/addrs/${e}/txs?from=0&to=${2*a}`).then(n=>{t.limit<=0&&(t.limit=n.items.length);var i=[];let a=n.totalItems-t.ignoreOld,o=0;for(let r=0;r{n(e)})}).catch(e=>{n(e)})})}})("object"==typeof module?module.exports:window.floBlockchainAPI={}); \ No newline at end of file diff --git a/scripts/floCloudAPI.js b/scripts/floCloudAPI.js index 921fa69..c2d2c3f 100644 --- a/scripts/floCloudAPI.js +++ b/scripts/floCloudAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCloudAPI v2.4.3 +(function (EXPORTS) { //floCloudAPI v2.4.3a /* FLO Cloud operations to send/request application data*/ 'use strict'; const floCloudAPI = EXPORTS; @@ -8,6 +8,7 @@ SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk", adminID: floGlobals.adminID, application: floGlobals.application, + SNStorageName: "SuperNodeStorage", callback: (d, e) => console.debug(d, e) }; @@ -58,6 +59,9 @@ SNStorageID: { get: () => DEFAULT.SNStorageID }, + SNStorageName: { + get: () => DEFAULT.SNStorageName + }, adminID: { get: () => DEFAULT.adminID }, diff --git a/scripts/floCloudAPI.min.js b/scripts/floCloudAPI.min.js deleted file mode 100644 index 2c1407d..0000000 --- a/scripts/floCloudAPI.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){"use strict";function t(e,n){if(!n||!e)return t.clear();let r=floCrypto.getPubKeyHex(n);if(!r||!floCrypto.verifyPubKey(r,e))return t.clear();let o=floCrypto.randInt(12,20);return y=floCrypto.randString(o),h=Crypto.AES.encrypt(n,y),m=r,p=e,p}function n(e){return new Promise((t,n)=>{if(!(e in I))return n(`${e} is not a supernode`);if(O.has(e))return n(`${e} is not active`);var r=new WebSocket("wss://"+I[e].uri+"/");r.onopen=(e=>t(r)),r.onerror=(t=>{O.add(e),n(`${e} is unavailable`)})})}function r(e,t=!1){return new Promise((o,c)=>{if(O.size===v.list.length)return c("Cloud offline");e in I||(e=v.closestNode(N(e))),n(e).then(e=>o(e)).catch(n=>{if(t)var i=v.prevNode(e);else i=v.nextNode(e);r(i,t).then(e=>o(e)).catch(e=>c(e))})})}function o(e,t){return new Promise((n,r)=>{if(O.has(e))return r(`${e} is not active`);let o,c="https://"+I[e].uri;"string"==typeof t?o=fetch(c+"?"+t):"object"==typeof t&&"POST"===t.method&&(o=fetch(c,t)),o.then(e=>{e.ok||400===e.status||500===e.status?n(e):r(e)}).catch(e=>r(e))})}function c(e,t,n=!1){return new Promise((r,i)=>{if(O.size===v.list.length)return i("Cloud offline");e in I||(e=v.closestNode(N(e))),o(e,t).then(e=>r(e)).catch(o=>{if(O.add(e),n)var a=v.prevNode(e);else a=v.nextNode(e);c(a,t,n).then(e=>r(e)).catch(e=>i(e))})})}function i(e,t,n="POST"){return new Promise((r,o)=>{let i;i="POST"===n?{method:"POST",body:JSON.stringify(t)}:new URLSearchParams(JSON.parse(JSON.stringify(t))).toString(),c(e,i).then(e=>{e.ok?e.json().then(e=>r(e)).catch(e=>o(e)):e.text().then(t=>o(e.status+": "+t)).catch(e=>o(e))}).catch(e=>o(e))})}function a(e,t,n){const o=void 0!==t.status?e=>{if(t.status)return e;{let n={};for(let r in e)t.trackList.includes(r)&&(n[r]=e[r]);return n}}:e=>{e=u(e);let n={},r=N(t.receiverID),o=t;for(let t in e){let c=e[t];o.atVectorClock&&o.atVectorClock!=t||!(o.atVectorClock||!o.lowerVectorClock||o.lowerVectorClock<=t)||!(o.atVectorClock||!o.upperVectorClock||o.upperVectorClock>=t)||o.afterTime&&!(o.afterTime{r(e).then(e=>{let r=floCrypto.randString(5);e.send(JSON.stringify(t)),e.onmessage=(e=>{let t=null,r=null;try{t=o(JSON.parse(e.data))}catch(t){r=e.data}finally{n(t,r)}}),w[r]=e,w[r].request=t,c(r)}).catch(e=>i(e))})}function l(e,t){try{console.log(t);let n=Object.keys(t).sort();for(let r of n)if(!(rD[e]&&(D[e]=t[n].log_time);compactIDB.writeData("lastVC",D[e],e),compactIDB.writeData("generalData",b[e],e)}catch(e){console.error(e)}}function u(e){return Array.isArray(e)||(e=[e]),Object.fromEntries(e.map(e=>(e.message=P(e.message),[e.vectorClock,e])))}const d=e,f={blockchainPrefix:35,SNStorageID:floGlobals.SNStorageID||"FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",adminID:floGlobals.adminID,application:floGlobals.application,callback:(e,t)=>console.debug(e,t)};var p,m,h,y,g,b,D;Object.defineProperties(t,{id:{get:()=>{if(!p)throw"User not set";return p}},public:{get:()=>{if(!m)throw"User not set";return m}},sign:{value:e=>{if(!h)throw"User not set";return floCrypto.signData(e,Crypto.AES.decrypt(h,y))}},clear:{value:()=>p=m=h=y=void 0}}),Object.defineProperties(d,{SNStorageID:{get:()=>f.SNStorageID},adminID:{get:()=>f.adminID},application:{get:()=>f.application},user:{get:()=>t}}),Object.defineProperties(floGlobals,{appObjects:{get:()=>g,set:e=>g=e},generalData:{get:()=>b,set:e=>b=e},generalDataset:{value:(e,t={})=>b[S(e,t)]},lastVC:{get:()=>D,set:e=>D=e}});var v,I={};Object.defineProperty(d,"nodes",{get:()=>JSON.parse(JSON.stringify(I))});const k=d.K_Bucket=function(e,t){const n=e=>{let t=bitjs.Base58.decode(e);t.shift(),t.splice(-4,4);let n=Crypto.util.bytesToHex(t),r=new BigInteger(n,16),o=r.toByteArrayUnsigned(),c=new Uint8Array(o);return c},r=new BuildKBucket({localNodeId:n(e)});t.forEach(e=>r.add({id:n(e),floID:e}));const o=t.map(e=>[r.distance(r.localNodeId,n(e)),e]).sort((e,t)=>e[0]-t[0]).map(e=>e[1]),c=this;Object.defineProperty(c,"tree",{get:()=>r}),Object.defineProperty(c,"list",{get:()=>Array.from(o)}),c.isNode=(e=>o.includes(e)),c.innerNodes=function(e,t){if(!o.includes(e)||!o.includes(t))throw Error("Given nodes are not supernode");let n=[];for(let r=o.indexOf(e)+1;o[r]!=t;r++)r-1?r[t++]=o[c]:c=o.length;return 1==t?r[0]:r},c.nextNode=function(e,t=1){let n=t||o.length;if(!o.includes(e))throw Error("Given node is not supernode");n||(n=o.length);let r=[];for(let t=0,c=o.indexOf(e)+1;te.floID);return 1==t?a[0]:a}};d.init=function(e){return new Promise((t,n)=>{try{I=e,v=new k(f.SNStorageID,Object.keys(I)),t("Cloud init successful")}catch(e){n(e)}})},Object.defineProperty(d,"kBucket",{get:()=>v});const O=new Set,w={};Object.defineProperty(d,"liveRequest",{get:()=>w}),Object.defineProperty(d,"inactive",{get:()=>O});const j=d.util={},C=j.encodeMessage=function(e){return btoa(unescape(encodeURIComponent(JSON.stringify(e))))},P=j.decodeMessage=function(e){return JSON.parse(decodeURIComponent(escape(atob(e))))},S=j.filterKey=function(e,t={}){return e+(t.comment?":"+t.comment:"")+"|"+(t.group||t.receiverID||f.adminID)+"|"+(t.application||f.application)},N=j.proxyID=function(e){if(e){var t;if(33==e.length||34==e.length){let n=bitjs.Base58.decode(e);t=n.slice(0,n.length-4);let r=n.slice(n.length-4),o=Crypto.SHA256(Crypto.SHA256(t,{asBytes:!0}),{asBytes:!0});o[0]!=r[0]||o[1]!=r[1]||o[2]!=r[2]||o[3]!=r[3]?t=void 0:t.shift()}else if(42==e.length||62==e.length){if("function"!=typeof coinjs)throw"library missing (lib_btc.js)";let n=coinjs.bech32_decode(e);n&&(t=n.data,t.shift(),t=coinjs.bech32_convert(t,5,8,!1),62==e.length&&(t=coinjs.bech32_convert(t,5,8,!1)))}else 66==e.length&&(t=ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(e),{asBytes:!0})));if(t){t.unshift(f.blockchainPrefix);let e=Crypto.SHA256(Crypto.SHA256(t,{asBytes:!0}),{asBytes:!0});return bitjs.Base58.encode(t.concat(e.slice(0,4)))}throw"Invalid address: "+e}},A={};Object.defineProperty(A,"get",{value:e=>JSON.parse(A[e])}),Object.defineProperty(A,"set",{value:e=>A[e]=JSON.stringify(g[e])}),d.setStatus=function(e={}){return new Promise((n,r)=>{let o=e.callback instanceof Function?e.callback:f.callback;var c={floID:t.id,application:e.application||f.application,time:Date.now(),status:!0,pubKey:t.public};let i=["time","application","floID"].map(e=>c[e]).join("|");c.sign=t.sign(i),a(e.refID||f.adminID,c,o).then(e=>n(e)).catch(e=>r(e))})},d.requestStatus=function(e,t={}){return new Promise((n,r)=>{Array.isArray(e)||(e=[e]);let o=t.callback instanceof Function?t.callback:f.callback,c={status:!1,application:t.application||f.application,trackList:e};a(t.refID||f.adminID,c,o).then(e=>n(e)).catch(e=>r(e))})};const B=d.sendApplicationData=function(e,n,r={}){return new Promise((o,c)=>{var a={senderID:t.id,receiverID:r.receiverID||f.adminID,pubKey:t.public,message:C(e),time:Date.now(),application:r.application||f.application,type:n,comment:r.comment||""};let l=["receiverID","time","application","type","message","comment"].map(e=>a[e]).join("|");a.sign=t.sign(l),i(a.receiverID,a).then(e=>o(e)).catch(e=>c(e))})},T=d.requestApplicationData=function(e,t={}){return new Promise((n,r)=>{var o={receiverID:t.receiverID||f.adminID,senderID:t.senderID||void 0,application:t.application||f.application,type:e,comment:t.comment||void 0,lowerVectorClock:t.lowerVectorClock||void 0,upperVectorClock:t.upperVectorClock||void 0,atVectorClock:t.atVectorClock||void 0,afterTime:t.afterTime||void 0,mostRecent:t.mostRecent||void 0};t.callback instanceof Function?a(o.receiverID,o,t.callback).then(e=>n(e)).catch(e=>r(e)):("POST"===t.method&&(o={time:Date.now(),request:o}),i(o.receiverID,o,t.method||"GET").then(e=>n(e)).catch(e=>r(e)))})};d.tagApplicationData=function(e,n,r={}){return new Promise((o,c)=>{if(!floGlobals.subAdmins.includes(t.id))return c("Only subAdmins can tag data");var a={receiverID:r.receiverID||f.adminID,requestorID:t.id,pubKey:t.public,time:Date.now(),vectorClock:e,tag:n};let l=["time","vectorClock","tag"].map(e=>a[e]).join("|");a.sign=t.sign(l),i(a.receiverID,a).then(e=>o(e)).catch(e=>c(e))})},d.noteApplicationData=function(e,n,r={}){return new Promise((o,c)=>{var a={receiverID:r.receiverID||f.adminID,requestorID:t.id,pubKey:t.public,time:Date.now(),vectorClock:e,note:n};let l=["time","vectorClock","note"].map(e=>a[e]).join("|");a.sign=t.sign(l),i(a.receiverID,a).then(e=>o(e)).catch(e=>c(e))})},d.sendGeneralData=function(e,t,n={}){return new Promise((r,o)=>{if(n.encrypt){let t=!0===n.encrypt?floGlobals.settings.encryptionKey:n.encrypt;e=floCrypto.encryptData(JSON.stringify(e),t)}B(e,t,n).then(e=>r(e)).catch(e=>o(e))})},d.requestGeneralData=function(e,t={}){return new Promise((n,r)=>{var o=S(e,t);if(D[o]=parseInt(D[o])||0,t.afterTime=t.afterTime||D[o],t.callback instanceof Function){let c=Object.create(t);c.callback=((e,n)=>{s(o,e),t.callback(e,n)}),T(e,c).then(e=>n(e)).catch(e=>r(e))}else T(e,t).then(e=>{s(o,u(e)),n(e)}).catch(e=>r(e))})},d.requestObjectData=function(e,t={}){return new Promise((n,r)=>{t.lowerVectorClock=t.lowerVectorClock||D[e]+1,t.senderID=[!1,null].includes(t.senderID)?null:t.senderID||floGlobals.subAdmins,t.mostRecent=!0,t.comment="RESET";let o=null;if(t.callback instanceof Function){let n=t.callback;o=((t,r)=>{l(e,t),n(t,r)}),delete t.callback}T(e,t).then(c=>{if(l(e,u(c)),delete t.comment,t.lowerVectorClock=D[e]+1,delete t.mostRecent,o){let c=Object.create(t);c.callback=o,T(e,c).then(e=>n(e)).catch(e=>r(e))}else T(e,t).then(t=>{l(e,u(t)),n(g[e])}).catch(e=>r(e))}).catch(e=>r(e))})},d.closeRequest=function(e){return new Promise((t,n)=>{let r=w[e];if(!r)return n("Request not found");r.onclose=(n=>{delete w[e],t("Request connection closed")}),r.close()})},d.resetObjectData=function(e,t={}){return new Promise((n,r)=>{let o={reset:g[e]};t.comment="RESET",B(o,e,t).then(t=>{A.set(e),n(t)}).catch(e=>r(e))})},d.updateObjectData=function(e,t={}){return new Promise((n,r)=>{let o={diff:V.find(A.get(e),g[e])};t.comment="UPDATE",B(o,e,t).then(t=>{A.set(e),n(t)}).catch(e=>r(e))})};var V=function(){const e=e=>e instanceof Date,t=e=>0===Object.keys(e).length,n=e=>null!=e&&"object"==typeof e,r=e=>n(e)&&!e.hasOwnProperty?{...e}:e,o=(c,i)=>{if(c===i)return{};if(!n(c)||!n(i))return i;const a=r(c),l=r(i);return e(a)||e(l)?a.valueOf()==l.valueOf()?{}:l:Object.keys(l).reduce((r,c)=>{if(a.hasOwnProperty(c)){const i=o(a[c],l[c]);return n(i)&&t(i)&&!e(i)?r:{...r,[c]:i}}return r},{})},c=(e,o)=>{if(e===o||!n(e)||!n(o))return{};const i=r(e),a=r(o);return Object.keys(a).reduce((e,r)=>{if(i.hasOwnProperty(r)){const o=c(i[r],a[r]);return n(o)&&t(o)?e:{...e,[r]:o}}return{...e,[r]:a[r]}},{})},i=(e,o)=>{if(e===o||!n(e)||!n(o))return{};const c=r(e),a=r(o);return Object.keys(c).reduce((e,r)=>{if(a.hasOwnProperty(r)){const o=i(c[r],a[r]);return n(o)&&t(o)?e:{...e,[r]:o}}return{...e,[r]:null}},{})},a=(e,t,n=!1)=>{for(var r in t)try{t[r].constructor==Object?e[r]=a(e[r],t[r],n):Array.isArray(t[r])?t[r].length<1?e[r]=t[r]:e[r]=a(e[r],t[r],n):e[r]=n&&null===t[r]?void 0:t[r]}catch(o){e[r]=n&&null===t[r]?void 0:t[r]}return e},l=e=>(Object.keys(e).forEach(t=>{var n=e[t];"object"==typeof n&&null!==n?e[t]=l(n):void 0===n&&delete e[t]}),Array.isArray(e)&&(e=e.filter(e=>void 0!==e)),e),s=(e,t)=>({added:c(e,t),deleted:i(e,t),updated:o(e,t)}),u=(e,t)=>(0!==Object.keys(t.updated).length&&(e=a(e,t.updated)),0!==Object.keys(t.deleted).length&&(e=a(e,t.deleted,!0),e=l(e)),0!==Object.keys(t.added).length&&(e=a(e,t.added)),e);return{find:s,merge:u}}()})("object"==typeof module?module.exports:window.floCloudAPI={}); \ No newline at end of file diff --git a/scripts/floCrypto.js b/scripts/floCrypto.js index 5259e61..f1e14d3 100644 --- a/scripts/floCrypto.js +++ b/scripts/floCrypto.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCrypto v2.3.3d +(function (EXPORTS) { //floCrypto v2.3.6a /* FLO Crypto Operators */ 'use strict'; const floCrypto = EXPORTS; @@ -152,6 +152,19 @@ newID: { get: () => generateNewID() }, + hashID: { + value: (str) => { + let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true }); + 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)); + } + }, tmpID: { get: () => { let bytes = Crypto.util.randomBytes(20); @@ -222,7 +235,7 @@ key.setCompressed(true); if (isfloID && pubKey_floID == key.getBitcoinAddress()) return true; - else if (!isfloID && pubKey_floID == key.getPubKeyHex()) + else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase()) return true; else return false; @@ -231,12 +244,36 @@ } } + floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) { + if (!Array.isArray(publicKeyList) || !publicKeyList.length) + return null; + if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length) + return null; + try { + var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures); + return multisig; + } catch { + return null; + } + } + + floCrypto.decodeRedeemScript = function (redeemScript) { + try { + var decoded = bitjs.transaction().decodeRedeemScript(redeemScript); + return decoded; + } catch { + return null; + } + } + //Check if the given flo-id is valid or not - floCrypto.validateFloID = function (floID) { + floCrypto.validateFloID = function (floID, regularOnly = false) { if (!floID) return false; try { let addr = new Bitcoin.Address(floID); + if (regularOnly && addr.version != Bitcoin.Address.standardVersion) + return false; return true; } catch { return false; @@ -266,22 +303,30 @@ return false; } - //Check the public-key for the address (any blockchain) + //Check the public-key (or redeem-script) 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; + let raw = decodeAddress(address); + if (!raw) + return; + let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true }))); + if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig + raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true })); + return pub_hash === raw.hex; } //Convert the given address (any blockchain) to equivalent floID - floCrypto.toFloID = function (address) { + floCrypto.toFloID = function (address, options = null) { if (!address) return; let raw = decodeAddress(address); if (!raw) return; + else if (options) { //if (optional) version check is passed + 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 @@ -291,6 +336,50 @@ return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4))); } + //Convert raw address bytes to floID + floCrypto.rawToFloID = function (raw_bytes) { + if (typeof raw_bytes === 'string') + raw_bytes = Crypto.util.hexToBytes(raw_bytes); + if (raw_bytes.length != 20) + return null; + 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))); + } + + //Convert the given multisig address (any blockchain) to equivalent multisig floID + floCrypto.toMultisigFloID = function (address, options = null) { + if (!address) + return; + let raw = decodeAddress(address); + if (!raw) + return; + else if (options) { //if (optional) version check is passed + 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; + } + if (typeof raw.bech_version !== 'undefined') { + if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes + //multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech + raw.bytes = ripemd160(raw.bytes, { + asBytes: true + }); + } + raw.bytes.unshift(bitjs.multisig); + 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) @@ -299,8 +388,13 @@ raw2 = decodeAddress(addr2); if (!raw1 || !raw2) return false; - else + else { + if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig + raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true })); + if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig + raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true })); return raw1.hex === raw2.hex; + } } const decodeAddress = floCrypto.decodeAddr = function (address) { @@ -320,7 +414,7 @@ hex: Crypto.util.bytesToHex(bytes), bytes } - } else if (address.length == 42) { //bech encoding + } else if (address.length == 42 || address.length == 62) { //bech encoding let decode = coinjs.bech32_decode(address); if (decode) { let bytes = decode.data; diff --git a/scripts/floCrypto.min.js b/scripts/floCrypto.min.js deleted file mode 100644 index f1ab9cd..0000000 --- a/scripts/floCrypto.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){"use strict";function t(e){let t=y();return e.modPow(BigInteger("3"),c).add(BigInteger("7")).mod(c).modPow(t,c)}function r(e){let r=Crypto.util.hexToBytes(e);const n=r.shift();let i=n%2;r.unshift(0);let o=new BigInteger(r),u=o.toString(),s=t(o),l=s.toString(),a=s.mod(BigInteger("2")),y=a.toString()%2;return i!==y&&(l=s.negate().mod(c).toString()),{x:u,y:l}}function n(){let e=ellipticCurveEncryption.senderRandom();var t=ellipticCurveEncryption.senderPublicString(e);return{privateKey:e,senderPublicKeyString:t}}function i(e,t){let n=r(e);var i=ellipticCurveEncryption.senderSharedKeyDerivation(n.x,n.y,t);return i}function o(e,t){return ellipticCurveEncryption.receiverSharedKeyDerivation(e.XValuePublicString,e.YValuePublicString,t)}function u(e,t=!1){let r=Bitcoin.Base58.decode(e);r.shift(),r.splice(-4,4),1==t&&r.pop(),r.unshift(0);let n=BigInteger(r).toString(),i=Crypto.util.bytesToHex(r);return{privateKeyDecimal:n,privateKeyHex:i}}const s=e,c=BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",16),l=EllipticCurve.getSECCurveByName("secp256k1"),a="‘ '\n’ '\n“ \"\n” \"\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>",y=()=>c.add(BigInteger.ONE).divide(BigInteger("4"));coinjs.compressed=!0,s.randInt=function(e,t){return e=Math.ceil(e),t=Math.floor(t),Math.floor(securedMathRandom()*(t-e+1))+e},s.randString=function(e,t=!0){for(var r="",n=t?"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():",i=0;id()},tmpID:{get:()=>{let e=Crypto.util.randomBytes(20);e.unshift(bitjs.pub);var t=Crypto.SHA256(Crypto.SHA256(e,{asBytes:!0}),{asBytes:!0}),r=t.slice(0,4);return bitjs.Base58.encode(e.concat(r))}}}),s.getPubKeyHex=function(e){if(!e)return null;var t=new Bitcoin.ECKey(e);return null==t.priv?null:(t.setCompressed(!0),t.getPubKeyHex())},s.getFloID=function(e){if(!e)return null;try{var t=new Bitcoin.ECKey(e);return null==t.priv&&t.setPub(e),t.getBitcoinAddress()}catch{return null}},s.getAddress=function(e,t=!1){if(!e)return;var r=new Bitcoin.ECKey(e);if(null==r.priv)return null;r.setCompressed(!0);let n=r.getPubKeyHex(),i=bitjs.Base58.decode(e)[0];switch(i){case coinjs.priv:return coinjs.bech32Address(n).address;case bitjs.priv:return bitjs.pubkey2address(n);default:return!t&&bitjs.pubkey2address(n)}},s.verifyPrivKey=function(e,t,r=!0){if(!e||!t)return!1;try{var n=new Bitcoin.ECKey(e);return null!=n.priv&&(n.setCompressed(!0),!(!r||t!=n.getBitcoinAddress())||!r&&t==n.getPubKeyHex())}catch{return null}},s.validateFloID=function(e){if(!e)return!1;try{new Bitcoin.Address(e);return!0}catch{return!1}},s.validateAddr=function(e,t=!0,r=!0){let n=f(e);return!!n&&(void 0!==n.version?0!=t&&!!(!0===t||!Array.isArray(t)&&t===n.version||Array.isArray(t)&&t.includes(n.version)):void 0!==n.bech_version&&(!1!==r&&!!(!0===r||!Array.isArray(r)&&r===n.bech_version||Array.isArray(r)&&r.includes(n.bech_version))))},s.verifyPubKey=function(e,t){let r=f(t),n=Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(e),{asBytes:!0})));return!!r&&n===r.hex},s.toFloID=function(e){if(!e)return;let t=f(e);if(!t)return;t.bytes.unshift(bitjs.pub);let r=Crypto.SHA256(Crypto.SHA256(t.bytes,{asBytes:!0}),{asBytes:!0});return bitjs.Base58.encode(t.bytes.concat(r.slice(0,4)))},s.isSameAddr=function(e,t){if(!e||!t)return;let r=f(e),n=f(t);return!(!r||!n)&&r.hex===n.hex};const f=s.decodeAddr=function(e){if(e){if(33==e.length||34==e.length){let t=bitjs.Base58.decode(e),r=t.slice(0,t.length-4),n=t.slice(t.length-4),i=Crypto.SHA256(Crypto.SHA256(r,{asBytes:!0}),{asBytes:!0});return i[0]!=n[0]||i[1]!=n[1]||i[2]!=n[2]||i[3]!=n[3]?null:{version:r.shift(),hex:Crypto.util.bytesToHex(r),bytes:r}}if(42==e.length){let t=coinjs.bech32_decode(e);if(t){let e=t.data,r=e.shift();return e=coinjs.bech32_convert(e,5,8,!1),{bech_version:r,hrp:t.hrp,hex:Crypto.util.bytesToHex(e),bytes:e}}return null}}};s.createShamirsSecretShares=function(e,t,r){try{if(e.length>0){var n=shamirSecretShare.str2hex(e),i=shamirSecretShare.share(n,t,r);return i}return!1}catch{return!1}};const p=s.retrieveShamirSecret=function(e){try{if(e.length>0){var t=shamirSecretShare.combine(e.slice(0,e.length));return t=shamirSecretShare.hex2str(t),t}return!1}catch{return!1}};s.verifyShamirsSecret=function(e,t){return t?p(e)===t:null};const h=s.validateASCII=function(e,t=!0){if("string"!=typeof e)return null;if(t){let t;for(let r=0;r127)return!1;return!0}{let t,r={};for(let n=0;n127)&&(t in r?r[e[n]].push(n):r[e[n]]=[n]);return!Object.keys(r).length||r}};s.convertToASCII=function(e,t="soft-remove"){let r=h(e,!1);if(!0===r)return e;if(null===r)return null;let n,i=e,o={};if(a.split("\n").forEach(e=>o[e[0]]=e.slice(2)),t=t.toLowerCase(),"hard-unicode"===t)n=(e=>`\\u${("000"+e.charCodeAt().toString(16)).slice(-4)}`);else if("soft-unicode"===t)n=(e=>o[e]||`\\u${("000"+e.charCodeAt().toString(16)).slice(-4)}`);else if("hard-remove"===t)n=(e=>"");else{if("soft-remove"!==t)return null;n=(e=>o[e]||"")}for(let e in r)i=i.replaceAll(e,n(e));return i},s.revertUnicode=function(e){return e.replace(/\\u[\dA-F]{4}/gi,e=>String.fromCharCode(parseInt(e.replace(/\\u/g,""),16)))}})("object"==typeof module?module.exports:window.floCrypto={}); \ No newline at end of file diff --git a/scripts/floDapps.js b/scripts/floDapps.js index f151fab..4d525a2 100644 --- a/scripts/floDapps.js +++ b/scripts/floDapps.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floDapps v2.3.2d +(function (EXPORTS) { //floDapps v2.4.0 /* General functions for FLO Dapps*/ 'use strict'; const floDapps = EXPORTS; @@ -144,11 +144,14 @@ } }); - var subAdmins, settings + var subAdmins, trustedIDs, settings; Object.defineProperties(floGlobals, { subAdmins: { get: () => subAdmins }, + trustedIDs: { + get: () => trustedIDs + }, settings: { get: () => settings }, @@ -181,6 +184,7 @@ credentials: {}, //for Dapps subAdmins: {}, + trustedIDs: {}, settings: {}, appObjects: {}, generalData: {}, @@ -228,24 +232,53 @@ }) } + const startUpOptions = { + cloud: true, + app_config: true, + } + + floDapps.startUpOptions = { + set app_config(val) { + if (val === true || val === false) + startUpOptions.app_config = val; + }, + get app_config() { return startUpOptions.app_config }, + + set cloud(val) { + if (val === true || val === false) + startUpOptions.cloud = val; + }, + get cloud() { return startUpOptions.cloud }, + } + const startUpFunctions = []; startUpFunctions.push(function readSupernodeListFromAPI() { return new Promise((resolve, reject) => { + if (!startUpOptions.cloud) + return resolve("No cloud for this app"); compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => { - floBlockchainAPI.readData(floCloudAPI.SNStorageID, { - ignoreOld: lastTx, - sentOnly: true, - pattern: "SuperNodeStorage" - }).then(result => { + var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName }; + if (typeof lastTx == 'number') //lastTx is tx count (*backward support) + query_options.ignoreOld = lastTx; + else if (typeof lastTx == 'string') //lastTx is txid of last tx + query_options.after = lastTx; + //fetch data from flosight + floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => { for (var i = result.data.length - 1; i >= 0; i--) { - var content = JSON.parse(result.data[i]).SuperNodeStorage; + var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName]; for (let sn in content.removeNodes) compactIDB.removeData("supernodes", sn, DEFAULT.root); for (let sn in content.newNodes) compactIDB.writeData("supernodes", content.newNodes[sn], sn, DEFAULT.root); + for (let sn in content.updateNodes) + compactIDB.readData("supernodes", sn, DEFAULT.root).then(r => { + r = r || {} + r.uri = content.updateNodes[sn]; + compactIDB.writeData("supernodes", r, sn, DEFAULT.root); + }); } - compactIDB.writeData("lastTx", result.totalTxs, floCloudAPI.SNStorageID, DEFAULT.root); + compactIDB.writeData("lastTx", result.lastItem, floCloudAPI.SNStorageID, DEFAULT.root); compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => { floCloudAPI.init(nodes) .then(result => resolve("Loaded Supernode list\n" + result)) @@ -258,12 +291,16 @@ startUpFunctions.push(function readAppConfigFromAPI() { return new Promise((resolve, reject) => { + if (!startUpOptions.app_config) + return resolve("No configs for this app"); compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => { - floBlockchainAPI.readData(DEFAULT.adminID, { - ignoreOld: lastTx, - sentOnly: true, - pattern: DEFAULT.application - }).then(result => { + var query_options = { sentOnly: true, pattern: DEFAULT.application }; + if (typeof lastTx == 'number') //lastTx is tx count (*backward support) + query_options.ignoreOld = lastTx; + else if (typeof lastTx == 'string') //lastTx is txid of last tx + query_options.after = lastTx; + //fetch data from flosight + floBlockchainAPI.readData(DEFAULT.adminID, query_options).then(result => { for (var i = result.data.length - 1; i >= 0; i--) { var content = JSON.parse(result.data[i])[DEFAULT.application]; if (!content || typeof content !== "object") @@ -274,16 +311,25 @@ if (Array.isArray(content.addSubAdmin)) for (var k = 0; k < content.addSubAdmin.length; k++) compactIDB.writeData("subAdmins", true, content.addSubAdmin[k]); + if (Array.isArray(content.removeTrustedID)) + for (var j = 0; j < content.removeTrustedID.length; j++) + compactIDB.removeData("trustedIDs", content.removeTrustedID[j]); + if (Array.isArray(content.addTrustedID)) + for (var k = 0; k < content.addTrustedID.length; k++) + compactIDB.writeData("trustedIDs", true, content.addTrustedID[k]); if (content.settings) for (let l in content.settings) compactIDB.writeData("settings", content.settings[l], l) } - compactIDB.writeData("lastTx", result.totalTxs, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root); + compactIDB.writeData("lastTx", result.lastItem, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root); compactIDB.readAllData("subAdmins").then(result => { subAdmins = Object.keys(result); - compactIDB.readAllData("settings").then(result => { - settings = result; - resolve("Read app configuration from blockchain"); + compactIDB.readAllData("trustedIDs").then(result => { + trustedIDs = Object.keys(result); + compactIDB.readAllData("settings").then(result => { + settings = result; + resolve("Read app configuration from blockchain"); + }) }) }) }) @@ -293,6 +339,8 @@ startUpFunctions.push(function loadDataFromAppIDB() { return new Promise((resolve, reject) => { + if (!startUpOptions.cloud) + return resolve("No cloud for this app"); var loadData = ["appObjects", "generalData", "lastVC"] var promises = [] for (var i = 0; i < loadData.length; i++) @@ -404,7 +452,8 @@ try { user_public = floCrypto.getPubKeyHex(privKey); user_id = floCrypto.getAddress(privKey); - floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI + if (startUpOptions.cloud) + floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI user_priv_wrap = () => checkIfPinRequired(key); let n = floCrypto.randInt(12, 20); aes_key = floCrypto.randString(n); @@ -567,6 +616,8 @@ floDapps.manageAppConfig = function (adminPrivKey, addList, rmList, settings) { return new Promise((resolve, reject) => { + if (!startUpOptions.app_config) + return reject("No configs for this app"); if (!Array.isArray(addList) || !addList.length) addList = undefined; if (!Array.isArray(rmList) || !rmList.length) rmList = undefined; if (!settings || typeof settings !== "object" || !Object.keys(settings).length) settings = undefined; @@ -589,6 +640,30 @@ }) } + floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) { + return new Promise((resolve, reject) => { + if (!startUpOptions.app_config) + return reject("No configs for this app"); + if (!Array.isArray(addList) || !addList.length) addList = undefined; + if (!Array.isArray(rmList) || !rmList.length) rmList = undefined; + if (!addList && !rmList) + return reject("No change in list") + var floData = { + [DEFAULT.application]: { + addTrustedID: addList, + removeTrustedID: rmList + } + } + var floID = floCrypto.getFloID(adminPrivKey) + if (floID != DEFAULT.adminID) + reject('Access Denied for Admin privilege') + else + floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey) + .then(result => resolve(['Updated App Configuration', result])) + .catch(error => reject(error)) + }) + } + const clearCredentials = floDapps.clearCredentials = function () { return new Promise((resolve, reject) => { compactIDB.clearData('credentials', DEFAULT.application).then(result => { diff --git a/scripts/floDapps.min.js b/scripts/floDapps.min.js deleted file mode 100644 index 7984100..0000000 --- a/scripts/floDapps.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){"use strict";function t(){return new Promise((e,a)=>{var r={lastTx:{},supernodes:{indexes:{uri:null,pubKey:null}}},o={credentials:{},subAdmins:{},settings:{},appObjects:{},generalData:{},lastVC:{}};t.appObs=t.appObs||{};for(let e in t.appObs)e in o||(o[e]=t.appObs[e]);Promise.all([compactIDB.initDB(c.application,o),compactIDB.initDB(c.root,r)]).then(t=>{compactIDB.setDefaultDB(c.application),e("IndexedDB App Storage Initated Successfully")}).catch(e=>a(e))})}function a(){return new Promise((e,t)=>{var a={contacts:{},pubKeys:{},messages:{}};compactIDB.initDB(m.db_name,a).then(t=>{e("UserDB Initated Successfully")}).catch(e=>t("Init userDB failed"))})}function r(){return new Promise((e,t)=>{for(var a=["contacts","pubKeys","messages"],r=[],o=0;o{for(var r=0;rt("Load userDB failed"))})}function o(){const e=e=>new Promise((t,a)=>{for(var r=[],o=0;o{var r=floCrypto.retrieveShamirSecret(e);r?t(r):a("Shares are insufficient or incorrect")}).catch(e=>{A(),location.reload()})}),t=(e,a=0,r=[])=>new Promise(o=>{if(a>=e.length)return o(r);var n=floCrypto.randInt(0,1e5);compactIDB.addData("credentials",e[a],n).then(c=>{r.push(n),t(e,a+1,r).then(e=>o(e))}).catch(n=>{t(e,a,r).then(e=>o(e))})}),a=()=>new Promise((a,r)=>{var o,n=localStorage.getItem(`${c.application}#privKey`);n?e(JSON.parse(n)).then(e=>a(e)).catch(e=>r(e)):D("PRIVATE_KEY").then(e=>{if(!e)return r("Empty Private Key");var t=floCrypto.getFloID(e);if(!t||!floCrypto.validateFloID(t))return r("Invalid Private Key");o=e}).catch(e=>{console.log(e,"Generating Random Keys"),o=floCrypto.generateNewID().privKey}).finally(e=>{if(o){var r=floCrypto.randInt(10,20),n=floCrypto.createShamirsSecretShares(o,r,r);t(n).then(e=>{localStorage.setItem(`${c.application}#privKey`,JSON.stringify(e));var r=floCrypto.generateNewID().privKey,n=floCrypto.randInt(10,20),i=floCrypto.createShamirsSecretShares(r,n,n);t(i),a(o)})}})}),r=e=>new Promise((t,a)=>{52==e.length?t(e):D("PIN/Password").then(r=>{try{let o=Crypto.AES.decrypt(e,r);t(o)}catch(e){a("Access Denied: Incorrect PIN/Password")}}).catch(e=>a("Access Denied: PIN/Password required"))});return new Promise((e,t)=>{a().then(a=>{r(a).then(o=>{try{u=floCrypto.getPubKeyHex(o),d=floCrypto.getAddress(o),floCloudAPI.user(d,o),l=(()=>r(a));let n=floCrypto.randInt(12,20);s=floCrypto.randString(n),i=Crypto.AES.encrypt(o,s),f=l,e("Login Credentials loaded successful")}catch(e){console.log(e),t("Corrupted Private Key")}}).catch(e=>t(e))}).catch(e=>t(e))})}const n=e,c={root:"floDapps",application:floGlobals.application,adminID:floGlobals.adminID};var i,s,l;Object.defineProperties(n,{application:{get:()=>c.application},adminID:{get:()=>c.adminID},root:{get:()=>c.root}});const p={get private(){if(!i)throw"User not logged in";return Crypto.AES.decrypt(i,s)}};var d,u,f;const m=n.user={get id(){if(!d)throw"User not logged in";return d},get public(){if(!u)throw"User not logged in";return u},get private(){if(f)return f instanceof Function?f():Crypto.AES.decrypt(f,s);throw"User not logged in"},sign:e=>floCrypto.signData(e,p.private),decrypt:e=>floCrypto.decryptData(e,p.private),encipher:e=>Crypto.AES.encrypt(e,p.private),decipher:e=>Crypto.AES.decrypt(e,p.private),get db_name(){return"floDapps#"+floCrypto.toFloID(m.id)},lock(){f=l},async unlock(){await m.private===p.private&&(f=i)},get_contact(e){if(!m.contacts)throw"Contacts not available";if(m.contacts[e])return m.contacts[e];{let t=floCrypto.decodeAddr(e).hex;for(let e in m.contacts)if(floCrypto.decodeAddr(e).hex==t)return m.contacts[e]}},get_pubKey(e){if(!m.pubKeys)throw"Contacts not available";if(m.pubKeys[e])return m.pubKeys[e];{let t=floCrypto.decodeAddr(e).hex;for(let e in m.pubKeys)if(floCrypto.decodeAddr(e).hex==t)return m.pubKeys[e]}},clear(){d=u=f=void 0,i=s=void 0,delete m.contacts,delete m.pubKeys,delete m.messages}};var y,h;Object.defineProperties(window,{myFloID:{get:()=>{try{return m.id}catch{return}}},myUserID:{get:()=>{try{return m.id}catch{return}}},myPubKey:{get:()=>{try{return m.public}catch{return}}},myPrivKey:{get:()=>{try{return m.private}catch{return}}}}),Object.defineProperties(floGlobals,{subAdmins:{get:()=>y},settings:{get:()=>h},contacts:{get:()=>m.contacts},pubKeys:{get:()=>m.pubKeys},messages:{get:()=>m.messages}});const g=[];g.push(function(){return new Promise((e,t)=>{compactIDB.readData("lastTx",floCloudAPI.SNStorageID,c.root).then(a=>{floBlockchainAPI.readData(floCloudAPI.SNStorageID,{ignoreOld:a,sentOnly:!0,pattern:"SuperNodeStorage"}).then(a=>{for(var r=a.data.length-1;r>=0;r--){var o=JSON.parse(a.data[r]).SuperNodeStorage;for(let e in o.removeNodes)compactIDB.removeData("supernodes",e,c.root);for(let e in o.newNodes)compactIDB.writeData("supernodes",o.newNodes[e],e,c.root)}compactIDB.writeData("lastTx",a.totalTxs,floCloudAPI.SNStorageID,c.root),compactIDB.readAllData("supernodes",c.root).then(a=>{floCloudAPI.init(a).then(t=>e("Loaded Supernode list\n"+t)).catch(e=>t(e))})})}).catch(e=>t(e))})}),g.push(function(){return new Promise((e,t)=>{compactIDB.readData("lastTx",`${c.application}|${c.adminID}`,c.root).then(t=>{floBlockchainAPI.readData(c.adminID,{ignoreOld:t,sentOnly:!0,pattern:c.application}).then(t=>{for(var a=t.data.length-1;a>=0;a--){var r=JSON.parse(t.data[a])[c.application];if(r&&"object"==typeof r){if(Array.isArray(r.removeSubAdmin))for(var o=0;o{y=Object.keys(t),compactIDB.readAllData("settings").then(t=>{h=t,e("Read app configuration from blockchain")})})})}).catch(e=>t(e))})}),g.push(function(){return new Promise((e,t)=>{for(var a=["appObjects","generalData","lastVC"],r=[],o=0;o{for(var r=0;rt(e))})});var D=e=>new Promise((t,a)=>{let r=prompt(`Enter ${e}: `);null===r?a(null):t(r)}),v=(e,t)=>e?console.log(t):console.error(t);const I=e=>new Promise((t,a)=>{g[e]().then(e=>{I.completed+=1,v(!0,`${e}\nCompleted ${I.completed}/${I.total} Startup functions`),t(!0)}).catch(e=>{I.failed+=1,v(!1,`${e}\nFailed ${I.failed}/${I.total} Startup functions`),a(!1)})});var b;const P=()=>new Promise((e,t)=>{b instanceof Function?b().then(t=>e("Mid startup function completed")).catch(e=>t("Mid startup function failed")):e("No mid startup function")}),S=e=>new Promise((t,a)=>{e.then(e=>{v(!0,e),t(e)}).catch(e=>{v(!1,e),a(e)})});n.launchStartUp=function(){return new Promise((e,n)=>{t().then(t=>{console.log(t),I.total=g.length,I.completed=0,I.failed=0;let c=new Promise((e,t)=>{Promise.all(g.map((e,t)=>I(t))).then(a=>{S(P()).then(t=>e(!0)).catch(e=>t(!1))})}),i=new Promise((e,t)=>{S(o()).then(o=>{S(a()).then(a=>{S(r()).then(t=>e(!0)).catch(e=>t(!1))}).catch(e=>t(!1))}).catch(e=>t(!1))});Promise.all([c,i]).then(t=>e("App Startup finished successful")).catch(e=>n("App Startup failed"))}).catch(e=>{v(!1,e),n("App database initiation failed")})})},n.addStartUpFunction=(e=>e instanceof Function&&!g.includes(e)&&g.push(e)),n.setMidStartup=(e=>e instanceof Function&&(b=e)),n.setCustomStartupLogger=(e=>e instanceof Function&&(v=e)),n.setCustomPrivKeyInput=(e=>e instanceof Function&&(D=e)),n.setAppObjectStores=(e=>t.appObs=e),n.storeContact=function(e,t){return new Promise((a,r)=>{if(!floCrypto.validateAddr(e))return r("Invalid floID!");compactIDB.writeData("contacts",t,e,m.db_name).then(r=>{m.contacts[e]=t,a("Contact stored")}).catch(e=>r(e))})},n.storePubKey=function(e,t){return new Promise((a,r)=>e in m.pubKeys?a("pubKey already stored"):floCrypto.validateAddr(e)?floCrypto.verifyPubKey(t,e)?void compactIDB.writeData("pubKeys",t,e,m.db_name).then(r=>{m.pubKeys[e]=t,a("pubKey stored")}).catch(e=>r(e)):r("Incorrect pubKey"):r("Invalid floID!"))},n.sendMessage=function(e,t){return new Promise((a,r)=>{let o={receiverID:e,application:c.root,comment:c.application};e in m.pubKeys&&(t=floCrypto.encryptData(JSON.stringify(t),m.pubKeys[e])),floCloudAPI.sendApplicationData(t,"Message",o).then(e=>a(e)).catch(e=>r(e))})},n.requestInbox=function(e){return new Promise((t,a)=>{let r=Object.keys(m.messages).sort().pop(),o={receiverID:m.id,application:c.root,lowerVectorClock:r+1},n=p.private;o.callback=((t,a)=>{for(let e in t){try{t[e].message instanceof Object&&"secret"in t[e].message&&(t[e].message=floCrypto.decryptData(t[e].message,n))}catch(e){}compactIDB.writeData("messages",t[e],e,m.db_name),m.messages[e]=t[e]}e instanceof Function&&e(t,a)}),floCloudAPI.requestApplicationData("Message",o).then(e=>t(e)).catch(e=>a(e))})},n.manageAppConfig=function(e,t,a,r){return new Promise((o,n)=>{if(Array.isArray(t)&&t.length||(t=void 0),Array.isArray(a)&&a.length||(a=void 0),r&&"object"==typeof r&&Object.keys(r).length||(r=void 0),!t&&!a&&!r)return n("No configuration change");var i={[c.application]:{addSubAdmin:t,removeSubAdmin:a,settings:r}},s=floCrypto.getFloID(e);s!=c.adminID?n("Access Denied for Admin privilege"):floBlockchainAPI.writeData(s,JSON.stringify(i),e).then(e=>o(["Updated App Configuration",e])).catch(e=>n(e))})};const A=n.clearCredentials=function(){return new Promise((e,t)=>{compactIDB.clearData("credentials",c.application).then(t=>{localStorage.removeItem(`${c.application}#privKey`),m.clear(),e("privKey credentials deleted!")}).catch(e=>t(e))})};n.deleteUserData=function(e=!1){return new Promise((t,a)=>{let r=[];r.push(compactIDB.deleteDB(m.db_name)),e&&r.push(A()),Promise.all(r).then(e=>t("User database(local) deleted")).catch(e=>a(e))})},n.deleteAppData=function(){return new Promise((e,t)=>{compactIDB.deleteDB(c.application).then(a=>{localStorage.removeItem(`${c.application}#privKey`),m.clear(),compactIDB.removeData("lastTx",`${c.application}|${c.adminID}`,c.root).then(t=>e("App database(local) deleted")).catch(e=>t(e))}).catch(e=>t(e))})},n.securePrivKey=function(e){return new Promise(async(t,a)=>{let r=localStorage.getItem(`${c.application}#privKey`);if(!r)return a("PrivKey not found");r=JSON.parse(r);let o=Crypto.AES.encrypt(await m.private,e),n=r.length,i=floCrypto.createShamirsSecretShares(o,n,n),s=[],l=(e,t)=>compactIDB.writeData("credentials",e,t,c.application);for(var p=0;pt("Private Key Secured")).catch(e=>a(e))})},n.verifyPin=function(e=null){const t=function(e){return new Promise((t,a)=>{for(var r=[],o=0;o{var r=floCrypto.retrieveShamirSecret(e);console.info(e,r),r?t(r):a("Shares are insufficient or incorrect")}).catch(e=>{A(),location.reload()})})};return new Promise((a,r)=>{var o=localStorage.getItem(`${c.application}#privKey`);console.info(o),o||r("No login credentials found"),t(JSON.parse(o)).then(t=>{if(52==t.length)null===e?a("Private key not secured"):r("Private key not secured");else{if(null===e)return r("PIN/Password required");try{Crypto.AES.decrypt(t,e);a("PIN/Password verified")}catch(e){r("Incorrect PIN/Password")}}}).catch(e=>r(e))})};const w=n.getNextGeneralData=function(e,t=null,a={}){var r=floCloudAPI.util.filterKey(e,a);t=t||w[r]||"0";var o={};if(floGlobals.generalData[r])for(let e in floGlobals.generalData[r])e>t&&(o[e]=JSON.parse(JSON.stringify(floGlobals.generalData[r][e])));else if(a.comment){let r=a.comment;delete a.comment;let n=floCloudAPI.util.filterKey(e,a);for(let e in floGlobals.generalData[n])e>t&&floGlobals.generalData[n][e].comment==r&&(o[e]=JSON.parse(JSON.stringify(floGlobals.generalData[n][e])))}if(a.decrypt){let e=!0===a.decrypt?p.private:a.decrypt;Array.isArray(e)||(e=[e]);for(let t in o){let a=o[t];try{if(a.message instanceof Object&&"secret"in a.message)for(let t of e)try{let e=floCrypto.decryptData(a.message,t);a.message=JSON.parse(e);break}catch(e){}}catch(e){}}}return w[r]=Object.keys(o).sort().pop(),o},C=n.syncData={};C.oldDevice=(()=>new Promise((e,t)=>{let a={contacts:m.contacts,pubKeys:m.pubKeys,messages:m.messages},r=Crypto.AES.encrypt(JSON.stringify(a),p.private),o={receiverID:m.id,application:c.root};floCloudAPI.sendApplicationData(r,"syncData",o).then(t=>e(t)).catch(e=>t(e))})),C.newDevice=(()=>new Promise((e,t)=>{var a={receiverID:m.id,senderID:m.id,application:c.root,mostRecent:!0};floCloudAPI.requestApplicationData("syncData",a).then(a=>{let r=Object.keys(a).sort().pop(),o=JSON.parse(Crypto.AES.decrypt(a[r].message,p.private)),n=[],c=(e,t,a)=>n.push(compactIDB.writeData(a,t,e,m.db_name));["contacts","pubKeys","messages"].forEach(e=>{for(let t in o[e])c(t,o[e][t],e),m[e][t]=o[e][t]}),Promise.all(n).then(t=>e("Sync data successful")).catch(e=>t(e))}).catch(e=>t(e))}))})("object"==typeof module?module.exports:window.floDapps={}); \ No newline at end of file diff --git a/scripts/floTokenAPI.js b/scripts/floTokenAPI.js new file mode 100644 index 0000000..2456b88 --- /dev/null +++ b/scripts/floTokenAPI.js @@ -0,0 +1,166 @@ +(function (EXPORTS) { //floTokenAPI v1.0.4a + /* Token Operator to send/receive tokens via blockchain using API calls*/ + 'use strict'; + const tokenAPI = EXPORTS; + + const DEFAULT = { + apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/", + currency: floGlobals.currency || "rupee" + } + + Object.defineProperties(tokenAPI, { + URL: { + get: () => DEFAULT.apiURL + }, + currency: { + get: () => DEFAULT.currency, + set: currency => DEFAULT.currency = currency + } + }); + + if (floGlobals.currency) tokenAPI.currency = floGlobals.currency; + + Object.defineProperties(floGlobals, { + currency: { + get: () => DEFAULT.currency, + set: currency => DEFAULT.currency = currency + } + }); + + const fetch_api = tokenAPI.fetch = function (apicall) { + return new Promise((resolve, reject) => { + console.debug(DEFAULT.apiURL + apicall); + fetch(DEFAULT.apiURL + apicall).then(response => { + if (response.ok) + response.json().then(data => resolve(data)); + else + reject(response) + }).catch(error => reject(error)) + }) + } + + const getBalance = tokenAPI.getBalance = function (floID, token = DEFAULT.currency) { + return new Promise((resolve, reject) => { + fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`) + .then(result => resolve(result.balance || 0)) + .catch(error => reject(error)) + }) + } + + tokenAPI.getTx = function (txID) { + return new Promise((resolve, reject) => { + fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => { + if (res.result === "error") + reject(res.description); + else if (!res.parsedFloData) + reject("Data piece (parsedFloData) missing"); + else if (!res.transactionDetails) + reject("Data piece (transactionDetails) missing"); + else + resolve(res); + }).catch(error => reject(error)) + }) + } + + tokenAPI.sendToken = function (privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) { + return new Promise((resolve, reject) => { + let senderID = floCrypto.getFloID(privKey); + if (typeof amount !== "number" || isNaN(amount) || amount <= 0) + return reject("Invalid amount"); + getBalance(senderID, token).then(bal => { + if (amount > bal) + return reject(`Insufficient ${token}# balance`); + floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID, options) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }); + } + + function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) { + return new Promise((resolve, reject) => { + var trx = bitjs.transaction(); + trx.addinput(utxo, vout, scriptPubKey) + trx.addoutput(receiverID, floBlockchainAPI.sendAmt); + trx.addflodata(`send ${amount} ${token}#`); + var signedTxHash = trx.sign(privKey, 1); + floBlockchainAPI.broadcastTx(signedTxHash) + .then(txid => resolve([receiverID, txid])) + .catch(error => reject([receiverID, error])) + }) + } + + //bulk transfer tokens + tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) { + return new Promise((resolve, reject) => { + if (typeof receivers !== 'object') + return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}") + + let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers); + let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id)); + let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0); + if (invalidReceivers.length) + return reject(`Invalid receivers: ${invalidReceivers}`); + else if (invalidAmount.length) + return reject(`Invalid amounts: ${invalidAmount}`); + + if (receiver_list.length == 0) + return reject("Receivers cannot be empty"); + + if (receiver_list.length == 1) { + let receiver = receiver_list[0], amount = amount_list[0]; + floTokenAPI.sendToken(privKey, amount, receiver, "", token) + .then(txid => resolve({ success: { [receiver]: txid } })) + .catch(error => reject(error)) + } else { + //check for token balance + floTokenAPI.getBalance(sender, token).then(token_balance => { + let total_token_amout = amount_list.reduce((a, e) => a + e, 0); + if (total_token_amout > token_balance) + return reject(`Insufficient ${token}# balance`); + + //split utxos + floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => { + //wait for the split utxo to get confirmation + floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => { + //send tokens using the split-utxo + var scriptPubKey = split_tx.vout[0].scriptPubKey.hex; + let promises = []; + for (let i in receiver_list) + promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey)); + Promise.allSettled(promises).then(results => { + let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value)); + let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason)); + resolve({ success, failed }); + }) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + } + + }) + } + + tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) { + return new Promise((resolve, reject) => { + fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`) + .then(result => resolve(result)) + .catch(error => reject(error)) + }) + } + + const util = tokenAPI.util = {}; + + util.parseTxData = function (txData) { + let parsedData = {}; + for (let p in txData.parsedFloData) + parsedData[p] = txData.parsedFloData[p]; + parsedData.sender = txData.transactionDetails.vin[0].addr; + for (let vout of txData.transactionDetails.vout) + if (vout.scriptPubKey.addresses[0] !== parsedData.sender) + parsedData.receiver = vout.scriptPubKey.addresses[0]; + parsedData.time = txData.transactionDetails.time; + return parsedData; + } + +})('object' === typeof module ? module.exports : window.floTokenAPI = {}); \ No newline at end of file diff --git a/scripts/lib.js b/scripts/lib.js index 9201f4b..e383403 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -1,4 +1,4 @@ -(function (GLOBAL) { //lib v1.3.1 +(function (GLOBAL) { //lib v1.4.2b 'use strict'; /* Utility Libraries required for Standard operations * All credits for these codes belong to their respective creators, moderators and owners. @@ -4349,20 +4349,18 @@ var bitjs = GLOBAL.bitjs = function () { }; - function ascii_to_hexa(str) { - var arr1 = []; - for (var n = 0, l = str.length; n < l; n++) { - var hex = Number(str.charCodeAt(n)).toString(16); - arr1.push(hex); - } - return arr1.join(''); - } - /* public vars */ bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23 bitjs.priv = 0xa3; //flochange - changed the prefix to FLO Mainnet Private key prefix 0xa3 + bitjs.multisig = 0x5e; //flochange - prefix for FLO Mainnet Multisig 0x5e bitjs.compressed = false; + if (GLOBAL.cryptocoin == 'FLO_TEST') { + bitjs.pub = 0x73; // flochange - changed the prefix to FLO TestNet PublicKey Prefix 0x73 + bitjs.priv = 0xa3; //flochange - changed the prefix to FLO TestNet Private key prefix 0xa3 + bitjs.multisig = 0xc6; //flochange - prefix for FLO TestNet Multisig 0xc6 + } + /* provide a privkey and return an WIF */ bitjs.privkey2wif = function (h) { var r = Crypto.util.hexToBytes(h); @@ -4461,7 +4459,46 @@ return B58.encode(r.concat(checksum)); } - bitjs.transaction = function () { + /* generate a multisig address from pubkeys and required signatures */ + bitjs.pubkeys2multisig = function (pubkeys, required) { + var s = []; + s.push(80 + required); //OP_1 + for (var i = 0; i < pubkeys.length; ++i) { + let bytes = Crypto.util.hexToBytes(pubkeys[i]); + s.push(bytes.length); + s = s.concat(bytes); + } + s.push(80 + pubkeys.length); //OP_1 + s.push(174); //OP_CHECKMULTISIG + + if (s.length > 520) { // too large + throw Error(`redeemScript size(=${s.length}) too large`) + } + + var x = ripemd160(Crypto.SHA256(s, { + asBytes: true + }), { + asBytes: true + }); + x.unshift(bitjs.multisig); + var r = x; + r = Crypto.SHA256(Crypto.SHA256(r, { + asBytes: true + }), { + asBytes: true + }); + var checksum = r.slice(0, 4); + var redeemScript = Crypto.util.bytesToHex(s); + var address = B58.encode(x.concat(checksum)); + + return { + 'address': address, + 'redeemScript': redeemScript, + 'size': s.length + }; + } + + bitjs.transaction = function (tx_data = undefined) { var btrx = {}; btrx.version = 2; //flochange look at this version btrx.inputs = []; @@ -4476,7 +4513,6 @@ 'hash': txid, 'index': index }; - //o.script = []; Signature and Public Key should be added after singning o.script = Crypto.util.hexToBytes(scriptPubKey); //push previous output pubkey script o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0); return this.inputs.push(o); @@ -4485,26 +4521,41 @@ btrx.addoutput = function (address, value) { var o = {}; var buf = []; - var addrDecoded = btrx.addressDecode(address); + var addr = this.addressDecode(address); o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10); - buf.push(118); //OP_DUP - buf.push(169); //OP_HASH160 - buf.push(addrDecoded.length); - buf = buf.concat(addrDecoded); // address in bytes - buf.push(136); //OP_EQUALVERIFY - buf.push(172); // OP_CHECKSIG + + if (addr.version === bitjs.pub) { // regular address + buf.push(118); //OP_DUP + buf.push(169); //OP_HASH160 + buf = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes + buf.push(136); //OP_EQUALVERIFY + buf.push(172); //OP_CHECKSIG + } else if (addr.version === bitjs.multisig) { // multisig address + buf.push(169); //OP_HASH160 + buf = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes + buf.push(135); //OP_EQUAL + } + o.script = buf; return this.outputs.push(o); } + // flochange - Added fn to assign flodata to tx + btrx.addflodata = function (data) { + //checks for valid flo-data string + if (typeof data !== "string") + throw Error("floData should be String"); + if (data.length > 1040) + throw Error("floData Character Limit Exceeded"); + if (bitjs.strToBytes(data).some(c => c < 32 || c > 127)) + throw Error("floData contains Invalid characters (only ASCII characters allowed"); - btrx.addflodata = function (txcomments) { // flochange - this whole function needs to be done - this.floData = txcomments; - return this.floData; //flochange .. returning the txcomments -- check if the function return will assign + this.floData = data; + return this.floData; } - // Only standard addresses + // Only standard addresses (standard multisig supported) btrx.addressDecode = function (address) { var bytes = B58.decode(address); var front = bytes.slice(0, bytes.length - 4); @@ -4515,7 +4566,10 @@ asBytes: true }).slice(0, 4); if (checksum + "" == back + "") { - return front.slice(1); + return { + version: front[0], + bytes: front.slice(1) + }; } } @@ -4740,6 +4794,83 @@ return KBigInt; }; + btrx.writeBytesToScriptBuffer = function (buf, bytes) { + if (bytes.length < 76) { //OP_PUSHDATA1 + buf.push(bytes.length); + } else if (bytes.length <= 0xff) { + buf.push(76); //OP_PUSHDATA1 + buf.push(bytes.length); + } else if (bytes.length <= 0xffff) { + buf.push(77); //OP_PUSHDATA2 + buf.push(bytes.length & 0xff); + buf.push((bytes.length >>> 8) & 0xff); + } else { + buf.push(78); //OP_PUSHDATA4 + buf.push(bytes.length & 0xff); + buf.push((bytes.length >>> 8) & 0xff); + buf.push((bytes.length >>> 16) & 0xff); + buf.push((bytes.length >>> 24) & 0xff); + } + buf = buf.concat(bytes); + return buf; + } + + btrx.parseScript = function (script) { + + var chunks = []; + var i = 0; + + function readChunk(n) { + chunks.push(script.slice(i, i + n)); + i += n; + }; + + while (i < script.length) { + var opcode = script[i++]; + if (opcode >= 0xF0) { + opcode = (opcode << 8) | script[i++]; + } + + var len; + if (opcode > 0 && opcode < 76) { //OP_PUSHDATA1 + readChunk(opcode); + } else if (opcode == 76) { //OP_PUSHDATA1 + len = script[i++]; + readChunk(len); + } else if (opcode == 77) { //OP_PUSHDATA2 + len = (script[i++] << 8) | script[i++]; + readChunk(len); + } else if (opcode == 78) { //OP_PUSHDATA4 + len = (script[i++] << 24) | (script[i++] << 16) | (script[i++] << 8) | script[i++]; + readChunk(len); + } else { + chunks.push(opcode); + } + + if (i < 0x00) { + break; + } + } + + return chunks; + } + + btrx.decodeRedeemScript = function (rs) { + if (typeof rs == "string") + rs = Crypto.util.hexToBytes(rs); + var script = this.parseScript(rs); + if (!(script[0] > 80 && script[script.length - 2] > 80 && script[script.length - 1] == 174)) //OP_CHECKMULTISIG + throw "Invalid RedeemScript"; + var r = {}; + r.required = script[0] - 80; + r.pubkeys = []; + for (var i = 1; i < script.length - 2; i++) + r.pubkeys.push(Crypto.util.bytesToHex(script[i])); + r.address = bitjs.pubkeys2multisig(r.pubkeys, r.required).address; + r.redeemscript = Crypto.util.bytesToHex(rs); + return r; + } + /* sign a "standard" input */ btrx.signinput = function (index, wif, sigHashType) { var key = bitjs.wif2pubkey(wif); @@ -4747,8 +4878,7 @@ var signature = this.transactionSig(index, wif, shType); var buf = []; var sigBytes = Crypto.util.hexToBytes(signature); - buf.push(sigBytes.length); - buf = buf.concat(sigBytes); + buf = this.writeBytesToScriptBuffer(buf, sigBytes); var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']); buf.push(pubKeyBytes.length); buf = buf.concat(pubKeyBytes); @@ -4756,15 +4886,98 @@ return true; } + /* sign a multisig input */ + btrx.signmultisig = function (index, wif, sigHashType) { + + var script = Array.from(this.inputs[index].script); + var redeemScript, sigsList = []; + + if (script[0] == 0) { //script with signatures + script = this.parseScript(script); + for (var i = 0; i < script.length; i++) { + if (Array.isArray(script[i])) { + if (script[i][0] == 48) //0x30 DERSequence + sigsList.push(script[i]); + else if (script[i][0] >= 80 && script[i][script[i].length - 1] == 174) //OP_CHECKMULTISIG + redeemScript = script[i]; + } + } + } else { //script = redeemscript + redeemScript = script; + } + + var pubkeyList = this.decodeRedeemScript(redeemScript).pubkeys; + var pubkey = bitjs.wif2pubkey(wif)['pubkey']; + if (!pubkeyList.includes(pubkey)) //wif not a part of this multisig + return false; + + pubkeyList = pubkeyList.map(pub => Crypto.util.hexToBytes(bitjs.pubkeydecompress(pub))); //decompress pubkeys + + var shType = sigHashType || 1; + this.inputs[index].script = redeemScript; //script to be signed is redeemscript + var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType)); + sigsList.push(signature); + + var buf = []; + buf.push(0); + + //verify signatures and order them (also remove duplicate sigs) + for (let x in pubkeyList) { + for (let y in sigsList) { + var sighash = Crypto.util.hexToBytes(this.transactionHash(index, sigsList[y].slice(-1)[0] * 1)); + if (bitjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) { + buf = this.writeBytesToScriptBuffer(buf, sigsList[y]); + break; //ensures duplicate sigs from same pubkey are not added + } + } + } + + //append redeemscript + buf = this.writeBytesToScriptBuffer(buf, redeemScript); + + this.inputs[index].script = buf; + return true; + } + /* sign inputs */ btrx.sign = function (wif, sigHashType) { var shType = sigHashType || 1; for (var i = 0; i < this.inputs.length; i++) { - this.signinput(i, wif, shType); + + var decodedScript = this.scriptDecode(i); + + if (decodedScript.type == "scriptpubkey" && decodedScript.signed == false) { //regular + var addr = bitjs.wif2address(wif)["address"];; + if (decodedScript.pubhash == Crypto.util.bytesToHex(this.addressDecode(addr).bytes)) //input belongs to wif + this.signinput(i, wif, shType); + } else if (decodedScript.type == "multisig") { //multisig + this.signmultisig(i, wif, shType); + } } return this.serialize(); } + // function to find type of the script in input + btrx.scriptDecode = function (index) { + var script = this.parseScript(this.inputs[index].script); + if (script.length == 5 && script[script.length - 1] == 172) { + //OP_DUP OP_HASH160 [address bytes] OP_EQUALVERIFY OP_CHECKSIG + // regular scriptPubkey (not signed) + return { type: 'scriptpubkey', signed: false, pubhash: Crypto.util.bytesToHex(script[2]) }; + } else if (script.length == 2 && script[0][0] == 48) { + //[signature] [pubkey] + //(probably) regular signed + return { type: 'scriptpubkey', signed: true }; + } else if (script[0] == 0 && script[script.length - 1][script[script.length - 1].length - 1] == 174) { + //0 [signatues] [redeemscript OP_CHECKMULTISIG] + // multisig with signature + return { type: 'multisig', rs: script[script.length - 1] }; + } else if (script[0] >= 80 && script[script.length - 1] == 174) { + //redeemscript: 80+ [pubkeys] OP_CHECKMULTISIG + // multisig without signature + return { type: 'multisig', rs: Array.from(this.inputs[index].script) }; + } + } /* serialize a transaction */ btrx.serialize = function () { @@ -4793,28 +5006,85 @@ } buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4)); - var flohex = ascii_to_hexa(this.floData); - var floDataCount = this.floData.length; - var floDataCountString; - //flochange -- creating unique data character count logic for floData. This string is prefixed before actual floData string in Raw Transaction - if (floDataCount < 16) { - floDataCountString = floDataCount.toString(16); - floDataCountString = "0" + floDataCountString; - } else if (floDataCount < 253) { - floDataCountString = floDataCount.toString(16); - } else if (floDataCount <= 1040) { - let floDataCountAdjusted = (floDataCount - 253) + parseInt("0xfd00fd"); - let floDataCountStringAdjusted = floDataCountAdjusted.toString(16); - floDataCountString = floDataCountStringAdjusted.substr(0, 2) + floDataCountStringAdjusted.substr(4, 2) + floDataCountStringAdjusted.substr(2, 2); - } else { - floDataCountString = "Character Limit Exceeded"; - } + //flochange -- append floData field + buffer = buffer.concat(bitjs.numToVarInt(this.floData.length)); + buffer = buffer.concat(bitjs.strToBytes(this.floData)) - return Crypto.util.bytesToHex(buffer) + floDataCountString + flohex; // flochange -- Addition of floDataCountString and floData in serialization + return Crypto.util.bytesToHex(buffer); } + /* deserialize a transaction */ + function deserialize(buffer) { + if (typeof buffer == "string") { + buffer = Crypto.util.hexToBytes(buffer) + } + var pos = 0; + + var readAsInt = function (bytes) { + if (bytes == 0) return 0; + pos++; + return buffer[pos - 1] + readAsInt(bytes - 1) * 256; + } + + var readVarInt = function () { + pos++; + if (buffer[pos - 1] < 253) { + return buffer[pos - 1]; + } + return readAsInt(buffer[pos - 1] - 251); + } + + var readBytes = function (bytes) { + pos += bytes; + return buffer.slice(pos - bytes, pos); + } + + var readVarString = function () { + var size = readVarInt(); + return readBytes(size); + } + + var bytesToStr = function (bytes) { + return bytes.map(b => String.fromCharCode(b)).join(''); + } + + const self = btrx; + + self.version = readAsInt(4); + + var ins = readVarInt(); + for (var i = 0; i < ins; i++) { + self.inputs.push({ + outpoint: { + hash: Crypto.util.bytesToHex(readBytes(32).reverse()), + index: readAsInt(4) + }, + script: readVarString(), + sequence: readAsInt(4) + }); + } + + var outs = readVarInt(); + for (var i = 0; i < outs; i++) { + self.outputs.push({ + value: bitjs.bytesToNum(readBytes(8)), + script: readVarString() + }); + } + + self.lock_time = readAsInt(4); + + //flochange - floData field + self.floData = bytesToStr(readVarString()); + + return self; + } + + //deserialize the data if passed + if (tx_data) + deserialize(tx_data); return btrx; @@ -4856,6 +5126,36 @@ else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1)); } + //flochange - adding fn to convert string (for flodata) to byte + bitjs.strToBytes = function (str) { + return str.split('').map(c => c.charCodeAt(0)); + } + + /* decompress an compressed public key */ + bitjs.pubkeydecompress = function (pubkey) { + if ((typeof (pubkey) == 'string') && pubkey.match(/^[a-f0-9]+$/i)) { + var curve = EllipticCurve.getSECCurveByName("secp256k1"); + try { + var pt = curve.curve.decodePointHex(pubkey); + var x = pt.getX().toBigInteger(); + var y = pt.getY().toBigInteger(); + + var publicKeyBytes = EllipticCurve.integerToBytes(x, 32); + publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)); + publicKeyBytes.unshift(0x04); + return Crypto.util.bytesToHex(publicKeyBytes); + } catch (e) { + // console.log(e); + return false; + } + } + return false; + } + + bitjs.verifySignature = function (hash, sig, pubkey) { + return Bitcoin.ECDSA.verify(hash, sig, pubkey); + } + /* clone an object */ bitjs.clone = function (obj) { if (obj == null || typeof (obj) != 'object') return obj; @@ -5020,17 +5320,25 @@ //https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js Bitcoin.Address = function (bytes) { - if (GLOBAL.cryptocoin == "FLO") - this.version = 0x23; // FLO mainnet public address - else if (GLOBAL.cryptocoin == "FLO_TEST") - this.version = 0x73; // FLO testnet public address if ("string" == typeof bytes) { - bytes = Bitcoin.Address.decodeString(bytes, this.version); + var d = Bitcoin.Address.decodeString(bytes); + bytes = d.hash; + if (d.version == Bitcoin.Address.standardVersion || d.version == Bitcoin.Address.multisigVersion) + this.version = d.version; + else throw "Version (prefix) " + d.version + " not supported!"; + } else { + this.version = Bitcoin.Address.standardVersion; } this.hash = bytes; }; - Bitcoin.Address.networkVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) // *this has no effect * + Bitcoin.Address.standardVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) + Bitcoin.Address.multisigVersion = 0x5e; // (FLO multisig 0x5e, 94D) + + if (GLOBAL.cryptocoin == "FLO_TEST") { + Bitcoin.Address.standardVersion = 0x73; // (FLO testnet 0x73, 115D), (Bitcoin Mainnet, 0x00, 0D) + Bitcoin.Address.multisigVersion = 0xc6; // (FLO testnet multisig 0xc6, 198D) + } /** * Serialize this object as a standard Bitcoin address. @@ -5059,7 +5367,7 @@ /** * Parse a Bitcoin address contained in a string. */ - Bitcoin.Address.decodeString = function (string, version) { + Bitcoin.Address.decodeString = function (string) { var bytes = Bitcoin.Base58.decode(string); var hash = bytes.slice(0, 21); var checksum = Crypto.SHA256(Crypto.SHA256(hash, { @@ -5075,11 +5383,12 @@ throw "Checksum validation failed!"; } - if (version != hash.shift()) { + /*if (version != hash.shift()) { throw "Version " + hash.shift() + " not supported!"; - } + }*/ - return hash; + var version = hash.shift(); + return { version, hash }; }; //https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js Bitcoin.ECDSA = (function () { @@ -6478,6 +6787,21 @@ }; } + //Return a Bech32 address for the multisig. Format is same as above + coinjs.pubkeys2MultisigAddressBech32 = function (pubkeys, required) { + var r = coinjs.pubkeys2MultisigAddress(pubkeys, required); + var program = Crypto.SHA256(Crypto.util.hexToBytes(r.redeemScript), { + asBytes: true + }); + var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true))); + return { + 'address': address, + 'redeemScript': r.redeemScript, + 'scripthash': Crypto.util.bytesToHex(program), + 'size': r.size + }; + } + /* new time locked address, provide the pubkey and time necessary to unlock the funds. when time is greater than 500000000, it should be a unix timestamp (seconds since epoch), otherwise it should be the block height required before this transaction can be released. @@ -6567,6 +6891,19 @@ }; } + coinjs.multisigBech32Address = function (redeemscript) { + var program = Crypto.SHA256(Crypto.util.hexToBytes(redeemscript), { + asBytes: true + }); + var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true))); + return { + 'address': address, + 'type': 'multisigBech32', + 'redeemScript': redeemscript, + 'scripthash': Crypto.util.bytesToHex(program) + }; + } + /* extract the redeemscript from a bech32 address */ coinjs.bech32redeemscript = function (address) { var r = false; @@ -6658,6 +6995,9 @@ } else if (o.version == coinjs.multisig) { // multisig address o.type = 'multisig'; + } else if (o.version == coinjs.multisigBech32) { // multisigBech32 added + o.type = 'multisigBech32'; + } else if (o.version == coinjs.priv) { // wifkey o.type = 'wifkey'; @@ -6698,11 +7038,16 @@ } } catch (e) { let bech32rs = coinjs.bech32redeemscript(addr); - if (bech32rs) { + if (bech32rs && bech32rs.length == 40) { return { 'type': 'bech32', 'redeemscript': bech32rs }; + } else if (bech32rs && bech32rs.length == 64) { + return { + 'type': 'multisigBech32', + 'redeemscript': bech32rs + }; } else { return false; } @@ -6711,7 +7056,7 @@ /* retreive the balance from a given address */ coinjs.addressBalance = function (address, callback) { - coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=bal&address=' + address + '&r=' + securedMathRandom(), callback, "GET"); + coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=bal&address=' + address + '&r=' + Math.random(), callback, "GET"); } /* decompress an compressed public key */ @@ -7328,11 +7673,39 @@ return r; } + /* decode the redeemscript of a multisignature transaction for Bech32*/ + r.decodeRedeemScriptBech32 = function (script) { + var r = false; + try { + var s = coinjs.script(Crypto.util.hexToBytes(script)); + if ((s.chunks.length >= 3) && s.chunks[s.chunks.length - 1] == 174) { //OP_CHECKMULTISIG + r = {}; + r.signaturesRequired = s.chunks[0] - 80; + var pubkeys = []; + for (var i = 1; i < s.chunks.length - 2; i++) { + pubkeys.push(Crypto.util.bytesToHex(s.chunks[i])); + } + r.pubkeys = pubkeys; + var multi = coinjs.pubkeys2MultisigAddressBech32(pubkeys, r.signaturesRequired); + r.address = multi['address']; + r.type = 'multisig__'; // using __ for now to differentiat from the other object .type == "multisig" + var rs = Crypto.util.bytesToHex(s.buffer); + r.redeemscript = rs; + + } + + } catch (e) { + // console.log(e); + r = false; + } + return r; + } + /* create output script to spend */ r.spendToScript = function (address) { var addr = coinjs.addressDecode(address); var s = coinjs.script(); - if (addr.type == "bech32") { + if (addr.type == "bech32" || addr.type == "multisigBech32") { s.writeOp(0); s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript)); } else if (addr.version == coinjs.multisig) { // multisig address @@ -7490,12 +7863,12 @@ /* list unspent transactions */ r.listUnspent = function (address, callback) { - coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=unspent&address=' + address + '&r=' + securedMathRandom(), callback, "GET"); + coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=unspent&address=' + address + '&r=' + Math.random(), callback, "GET"); } /* list transaction data */ r.getTransaction = function (txid, callback) { - coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=bitcoin&request=gettransaction&txid=' + txid + '&r=' + securedMathRandom(), callback, "GET"); + coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=bitcoin&request=gettransaction&txid=' + txid + '&r=' + Math.random(), callback, "GET"); } /* add unspent to transaction */ @@ -7525,7 +7898,7 @@ var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue; var scr = script || u.getElementsByTagName("script")[0].childNodes[0].nodeValue; - if (segwit) { + if (segwit) { //also for MULTISIG_BECH32 (p2wsh-multisig)(script = redeemscript; for p2wsh-multisig) /* this is a small hack to include the value with the redeemscript to make the signing procedure smoother. It is not standard and removed during the signing procedure. */ @@ -7664,7 +8037,7 @@ // start redeem script check var extract = this.extractScriptKey(index); - if (extract['type'] != 'segwit') { + if (extract['type'] != 'segwit' && extract['type'] != 'multisig_bech32') { return { 'result': 0, 'fail': 'redeemscript', @@ -7693,6 +8066,8 @@ scriptcode = scriptcode.slice(1); scriptcode.unshift(25, 118, 169); scriptcode.push(136, 172); + } else if (scriptcode[0] > 80) { + scriptcode.unshift(scriptcode.length) } var value = coinjs.numToBytes(extract['value'], 8); @@ -7855,11 +8230,26 @@ 'signatures': 0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer) }; + } else if (this.ins[index].script.chunks.length == 3 && this.ins[index].script.chunks[0][0] >= 80 && this.ins[index].script.chunks[0][this.ins[index].script.chunks[0].length - 1] == 174 && this.ins[index].script.chunks[1] == 0) { //OP_CHECKMULTISIG_BECH32 + // multisig bech32 script + let last_index = this.ins[index].script.chunks.length - 1; + var value = -1; + if (last_index >= 2 && this.ins[index].script.chunks[last_index].length == 8) { + value = coinjs.bytesToNum(this.ins[index].script.chunks[last_index]); // value found encoded in transaction (THIS IS NON STANDARD) + } + var sigcount = (!this.witness[index]) ? 0 : this.witness[index].length - 2; + return { + 'type': 'multisig_bech32', + 'signed': 'false', + 'signatures': sigcount, + 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), + 'value': value + }; } else if (this.ins[index].script.chunks.length == 0) { // empty //bech32 witness check - var signed = ((this.witness[index]) && this.witness[index].length == 2) ? 'true' : 'false'; - var sigs = (signed == 'true') ? 1 : 0; + var signed = ((this.witness[index]) && this.witness[index].length >= 2) ? 'true' : 'false'; + var sigs = (signed == 'true') ? (!this.witness[index][0] ? this.witness[index].length - 2 : 1) : 0; return { 'type': 'empty', 'signed': signed, @@ -8038,6 +8428,71 @@ return true; } + r.signmultisig_bech32 = function (index, wif, sigHashType) { + + function scriptListPubkey(redeemScript) { + var r = {}; + for (var i = 1; i < redeemScript.chunks.length - 2; i++) { + r[i] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(Crypto.util.bytesToHex(redeemScript.chunks[i]))); + } + return r; + } + + function scriptListSigs(sigList) { + let r = {}; + var c = 0; + if (Array.isArray(sigList)) { + for (let i = 1; i < sigList.length - 1; i++) { + c++; + r[c] = Crypto.util.hexToBytes(sigList[i]); + } + } + return r; + } + + var redeemScript = Crypto.util.bytesToHex(this.ins[index].script.chunks[0]); //redeemScript + + if (!coinjs.isArray(this.witness)) { + this.witness = new Array(this.ins.length); + this.witness.fill([]); + } + + var pubkeyList = scriptListPubkey(coinjs.script(redeemScript)); + var sigsList = scriptListSigs(this.witness[index]); + let decode_rs = coinjs.script().decodeRedeemScriptBech32(redeemScript); + + var shType = sigHashType || 1; + var txhash = this.transactionHashSegWitV0(index, shType); + + if (txhash.result == 1 && decode_rs.pubkeys.includes(coinjs.wif2pubkey(wif)['pubkey'])) { + + var segwitHash = Crypto.util.hexToBytes(txhash.hash); + var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType, segwitHash)); //CHECK THIS + + sigsList[coinjs.countObject(sigsList) + 1] = signature; + + var w = []; + + for (let x in pubkeyList) { + for (let y in sigsList) { + var sighash = this.transactionHashSegWitV0(index, sigsList[y].slice(-1)[0] * 1).hash + sighash = Crypto.util.hexToBytes(sighash); + if (coinjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) { + w.push((Crypto.util.bytesToHex(sigsList[y]))) + } + } + } + + // when enough signatures collected, remove any non standard data we store, i.e. input value + if (w.length >= decode_rs.signaturesRequired) { + this.ins[index].script = coinjs.script(); + } + w.unshift(0); + w.push(redeemScript); + this.witness[index] = w; + } + } + /* sign a multisig input */ r.signmultisig = function (index, wif, sigHashType) { @@ -8187,6 +8642,9 @@ } else if (d['type'] == 'multisig') { this.signmultisig(i, wif, shType); + } else if (d['type'] == 'multisig_bech32' && d['signed'] == "false") { + this.signmultisig_bech32(i, wif, shType); + } else if (d['type'] == 'segwit') { this.signsegwit(i, wif, shType); @@ -8240,6 +8698,63 @@ return Crypto.util.bytesToHex(buffer); } + //Utility funtion added to directly compute signatures without transaction index + r.transactionSigNoIndex = function (wif, sigHashType, txhash) { + + function serializeSig(r, s) { + var rBa = r.toByteArraySigned(); + var sBa = s.toByteArraySigned(); + + var sequence = []; + sequence.push(0x02); // INTEGER + sequence.push(rBa.length); + sequence = sequence.concat(rBa); + + sequence.push(0x02); // INTEGER + sequence.push(sBa.length); + sequence = sequence.concat(sBa); + + sequence.unshift(sequence.length); + sequence.unshift(0x30); // SEQUENCE + + return sequence; + } + + var shType = sigHashType || 1; + var hash = Crypto.util.hexToBytes(txhash); + + if (hash) { + var curve = EllipticCurve.getSECCurveByName("secp256k1"); + var key = coinjs.wif2privkey(wif); + var priv = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(key['privkey'])); + var n = curve.getN(); + var e = BigInteger.fromByteArrayUnsigned(hash); + + var badrs = 0 + do { + var k = this.deterministicK(wif, hash, badrs); + var G = curve.getG(); + var Q = G.multiply(k); + var r = Q.getX().toBigInteger().mod(n); + var s = k.modInverse(n).multiply(e.add(priv.multiply(r))).mod(n); + badrs++ + } while (r.compareTo(BigInteger.ZERO) <= 0 || s.compareTo(BigInteger.ZERO) <= 0); + + // Force lower s values per BIP62 + var halfn = n.shiftRight(1); + if (s.compareTo(halfn) > 0) { + s = n.subtract(s); + }; + + var sig = serializeSig(r, s); + sig.push(parseInt(shType, 10)); + + return Crypto.util.bytesToHex(sig); + } else { + return false; + } + } + /* deserialize a transaction */ r.deserialize = function (buffer) { if (typeof buffer == "string") { @@ -8582,12 +9097,116 @@ return count; } + //Nine utility functions added for generating transaction hashes and verification of signatures + coinjs.changeEndianness = (string) => { + const result = []; + let len = string.length - 2; + while (len >= 0) { + result.push(string.substr(len, 2)); + len -= 2; + } + return result.join(''); + } + + coinjs.getTransactionHash = function (transaction_in_hex, changeOutputEndianess) { + var x1, x2, x3, x4, x5; + x1 = Crypto.util.hexToBytes(transaction_in_hex); + x2 = Crypto.SHA256(x1); + x3 = Crypto.util.hexToBytes(x2); + x4 = Crypto.SHA256(x3); + x5 = coinjs.changeEndianness(x4); + if (changeOutputEndianess == true) { x5 = x5 } else if ((typeof changeOutputEndianess == 'undefined') || (changeOutputEndianess == false)) { x5 = x4 }; + return x5; + } + + coinjs.compressedToUncompressed = function (compressed) { + var t1, t2; + var curve = EllipticCurve.getSECCurveByName("secp256k1"); + t1 = curve.curve.decodePointHex(compressed); + t2 = curve.curve.encodePointHex(t1); + return t2; + } + + coinjs.uncompressedToCompressed = function (uncompressed) { + var t1, t2, t3; + t1 = uncompressed.charAt(uncompressed.length - 1) + t2 = parseInt(t1, 10); + //Check if the last digit is odd + if (t2 % 2 == 1) { t3 = "03"; } else { t3 = "02" }; + return t3 + uncompressed.substr(2, 64); + } + + coinjs.verifySignatureHex = function (hashHex, sigHex, pubHexCompressed) { + var h1, s1, p1, p2; + h1 = Crypto.util.hexToBytes(hashHex); + s1 = Crypto.util.hexToBytes(sigHex); + p1 = coinjs.compressedToUncompressed(pubHexCompressed); + p2 = Crypto.util.hexToBytes(p1); + + return coinjs.verifySignature(h1, s1, p2); + } + + coinjs.generateBitcoinSignature = function (private_key, hash, sighash_type_int = 1) { + var wif, tx1; + if (private_key.length < 60) { wif = private_key } else { wif = coinjs.privkey2wif(private_key) }; + tx1 = coinjs.transaction(); + return tx1.transactionSigNoIndex(wif, sighash_type_int, hash); + } + + coinjs.dSHA256 = function (data) { + var t1, t2, t3; + t1 = Crypto.SHA256(Crypto.util.hexToBytes(data)); + t2 = Crypto.util.hexToBytes(t1); + t3 = Crypto.SHA256(t2); + return t3; + } + + coinjs.fromBitcoinAmountFormat = function (data) { + var x1, x2, x3; + x1 = coinjs.changeEndianness(data); + x2 = parseInt(x1, 16); + x3 = x2 / (10 ** 8); + return x3; + } + + coinjs.toBitcoinAmountFormat = function (countBitcoin) { + var t2, t3, t4, t5; + t2 = countBitcoin * 10 ** 8; + t3 = t2.toString(16); + t4 = coinjs.changeEndianness(t3); + t5 = t4.padEnd(16, "0"); + return t5; + } + + coinjs.scriptcodeCreatorBasic = function (scriptpubkey) { + var t1, t2, t3, t4; + if (scriptpubkey.substr(0, 4) == "0014") { + //Scriptpubkey case + t1 = scriptpubkey.slice(2); + t2 = "1976a9" + t1 + "88ac"; + } else { + //Redeemscript case + t3 = (scriptpubkey.length) / 2; + t4 = t3.toString(16); + t2 = t4 + scriptpubkey; + } + return t2; + } + + coinjs.ripemd160sha256 = function (data) { + var t1, t2; + + t1 = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(data), { asBytes: true }), { asBytes: true }); + t2 = Crypto.util.bytesToHex(t1) + return t2; + } + coinjs.random = function (length) { var r = ""; var l = length || 25; var chars = "!$%^&*()_+{}:@~?><|\./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; for (let x = 0; x < l; x++) { - r += chars.charAt(Math.floor(securedMathRandom() * 62)); + r += chars.charAt(Math.floor(Math.random() * 62)); } return r; } diff --git a/scripts/lib.min.js b/scripts/lib.min.js deleted file mode 100644 index af0e03f..0000000 --- a/scripts/lib.min.js +++ /dev/null @@ -1,5 +0,0 @@ -(function(t){"use strict";t.cryptocoin=("undefined"==typeof floGlobals?null:floGlobals.blockchain)||"FLO";const e=function(){if("function"==typeof require){const t=require("crypto");return function(e){var r=t.randomBytes(e.length);return e.set(r),e}}if(t.crypto&&t.crypto.getRandomValues)return function(e){return t.crypto.getRandomValues(e)};throw Error("Unable to define getRandomBytes")}();var r,n,s,a,u,c,h,f,p,l,y,d,g,m;t.securedMathRandom=function(){if("function"==typeof require){const t=require("crypto");return function(){return t.randomBytes(4).readUInt32LE()/4294967295}}if(t.crypto&&t.crypto.getRandomValues)return function(){return t.crypto.getRandomValues(new Uint32Array(1))[0]/4294967295};throw Error("Unable to define securedMathRandom")}(),function(){var e,r,i,n,s,o,a,u,c,h,f,p=t.Crypto={};e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=p.util={rotl:function(t,e){return t<>>32-e},rotr:function(t,e){return t<<32-e|t>>>e},endian:function(t){if(t.constructor==Number)return 16711935&r.rotl(t,8)|4278255360&r.rotl(t,24);for(var e=0;e0;t--)e.push(Math.floor(256*securedMathRandom()));return e},bytesToWords:function(t){for(var e=[],r=0,i=0;r>>5]|=(255&t[r])<<24-i%32;return e},wordsToBytes:function(t){for(var e=[],r=0;r<32*t.length;r+=8)e.push(t[r>>>5]>>>24-r%32&255);return e},bytesToHex:function(t){for(var e=[],r=0;r>>4).toString(16)),e.push((15&t[r]).toString(16));return e.join("")},hexToBytes:function(t){for(var e=[],r=0;r>>6*(3-s)&63)):r.push("=");return r.join("")},base64ToBytes:function(t){t=t.replace(/[^A-Z0-9+\/]/gi,"");for(var r=[],i=0,n=0;i>>6-2*n);return r}},i=p.charenc={},i.UTF8={stringToBytes:function(t){return n.stringToBytes(unescape(encodeURIComponent(t)))},bytesToString:function(t){return decodeURIComponent(escape(n.bytesToString(t)))}},n=i.Binary={stringToBytes:function(t){for(var e=[],r=0;r>5]|=128<<24-n%32,r[15+(n+64>>>9<<4)]=n;for(var f=0;f>>31}var b=(o<<5|o>>>27)+h+(s[v]>>>0)+(v<20?1518500249+(a&u|~a&c):v<40?1859775393+(a^u^c):v<60?(a&u|a&c|u&c)-1894007588:(a^u^c)-899497514);h=c,c=u,u=a<<30|a>>>2,a=o,o=b}o+=p,a+=l,u+=y,c+=d,h+=g}return[o,a,u,c,h]},s._blocksize=16,s._digestsize=20}(),function(){var t=p,e=t.util,r=t.charenc,i=r.UTF8,n=r.Binary;t.HMAC=function(t,r,s,o){r.constructor==String&&(r=i.stringToBytes(r)),s.constructor==String&&(s=i.stringToBytes(s)),s.length>4*t._blocksize&&(s=t(s,{asBytes:!0}));for(var a=s.slice(0),u=s.slice(0),c=0;c<4*t._blocksize;c++)a[c]^=92,u[c]^=54;var h=t(a.concat(t(u.concat(r),{asBytes:!0})),{asBytes:!0});return o&&o.asBytes?h:o&&o.asString?n.bytesToString(h):e.bytesToHex(h)}}(),s=p,o=s.util,a=s.charenc,u=a.UTF8,c=a.Binary,h=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],f=s.SHA256=function(t,e){var r=o.wordsToBytes(f._sha256(t));return e&&e.asBytes?r:e&&e.asString?c.bytesToString(r):o.bytesToHex(r)},f._sha256=function(t){t.constructor==String&&(t=u.stringToBytes(t));var e,r,i,n,s,a,c,f,p,l,y,d=o.bytesToWords(t),g=8*t.length,v=(t=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],[]);for(d[g>>5]|=128<<24-g%32,d[15+(g+64>>9<<4)]=g,f=0;f>>7)^(l<<14|l>>>18)^l>>>3)+(v[p-7]>>>0)+((y<<15|y>>>17)^(y<<13|y>>>19)^y>>>10)+(v[p-16]>>>0)),y=g&e^g&r^e&r;var m=(g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22);l=(c>>>0)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))+(n&s^~n&a)+h[p]+(v[p]>>>0),y=m+y,c=a,a=s,s=n,n=i+l>>>0,i=r,r=e,e=g,g=l+y>>>0}t[0]+=g,t[1]+=e,t[2]+=r,t[3]+=i,t[4]+=n,t[5]+=s,t[6]+=a,t[7]+=c}return t},f._blocksize=16,f._digestsize=32,function(){var t=p,e=t.util,r=t.charenc,i=r.UTF8,n=r.Binary;t.HMAC=function(t,r,s,o){r.constructor==String&&(r=i.stringToBytes(r)),s.constructor==String&&(s=i.stringToBytes(s)),s.length>4*t._blocksize&&(s=t(s,{asBytes:!0}));for(var a=s.slice(0),u=(s=s.slice(0),0);u<4*t._blocksize;u++)a[u]^=92,s[u]^=54;return t=t(a.concat(t(s.concat(r),{asBytes:!0})),{asBytes:!0}),o&&o.asBytes?t:o&&o.asString?n.bytesToString(t):e.bytesToHex(t)}}()}(),function(){var r=t.SecureRandom=function(){};if(r.state,r.pool,r.pptr,r.poolCopyOnInit,r.poolSize=256,r.prototype.nextBytes=function(i){var n;if(e&&t.Uint8Array)try{var s=new Uint8Array(i.length);for(e(s),n=0;n>8),r.seedInt8(t>>16),r.seedInt8(t>>24)},r.seedInt16=function(t){r.seedInt8(t),r.seedInt8(t>>8)},r.seedInt8=function(t){r.pool[r.pptr++]^=255&t,r.pptr>=r.poolSize&&(r.pptr-=r.poolSize)},r.ArcFour=function(){function t(){this.i=0,this.j=0,this.S=new Array}function e(t){var e,r,i;for(e=0;e<256;++e)this.S[e]=e;for(r=0,e=0;e<256;++e)r=r+this.S[e]+t[e%t.length]&255,i=this.S[e],this.S[e]=this.S[r],this.S[r]=i;this.i=0,this.j=0}function r(){var t;return this.i=this.i+1&255,this.j=this.j+this.S[this.i]&255,t=this.S[this.i],this.S[this.i]=this.S[this.j],this.S[this.j]=t,this.S[t+this.S[this.i]&255]}return t.prototype.init=e,t.prototype.next=r,new t},null==r.pool){var i;if(r.pool=new Array,r.pptr=0,e&&t.Uint8Array)try{var n=new Uint8Array(r.poolSize);for(e(n),i=0;i>>8,r.pool[r.pptr++]=255&i;r.pptr=Math.floor(r.poolSize*securedMathRandom()),r.seedTime();var s="";s+=t.screen.height*t.screen.width*t.screen.colorDepth,s+=t.screen.availHeight*t.screen.availWidth*t.screen.pixelDepth;var o=new Date,a=o.getTimezoneOffset();s+=a,s+=navigator.userAgent;for(var u="",c=0;c>>32-e}var a=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],u=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],c=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],h=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11],f=[0,1518500249,1859775393,2400959708,2840853838],p=[1352829926,1548603684,1836072691,2053994217,0],l=function(t){for(var e=[],r=0,i=0;r>>5]|=t[r]<<24-i%32;return e},y=function(t){for(var e=[],r=0;r<32*t.length;r+=8)e.push(t[r>>>5]>>>24-r%32&255);return e},d=function(t,l,y){for(var d=0;d<16;d++){var g=y+d,v=l[g];l[g]=16711935&(v<<8|v>>>24)|4278255360&(v<<24|v>>>8)}var m,b,B,w,T,C,S,k,I,F,A;C=m=t[0],S=b=t[1],k=B=t[2],I=w=t[3],F=T=t[4];for(d=0;d<80;d+=1)A=m+l[y+a[d]]|0,A+=d<16?e(b,B,w)+f[0]:d<32?r(b,B,w)+f[1]:d<48?i(b,B,w)+f[2]:d<64?n(b,B,w)+f[3]:s(b,B,w)+f[4],A|=0,A=o(A,c[d]),A=A+T|0,m=T,T=w,w=o(B,10),B=b,b=A,A=C+l[y+u[d]]|0,A+=d<16?s(S,k,I)+p[0]:d<32?n(S,k,I)+p[1]:d<48?i(S,k,I)+p[2]:d<64?r(S,k,I)+p[3]:e(S,k,I)+p[4],A|=0,A=o(A,h[d]),A=A+F|0,C=F,F=I,I=o(k,10),k=S,S=A;A=t[1]+B+I|0,t[1]=t[2]+w+F|0,t[2]=t[3]+T+C|0,t[3]=t[4]+m+S|0,t[4]=t[0]+b+k|0,t[0]=A};t.ripemd160=function(t){var e=[1732584193,4023233417,2562383102,271733878,3285377520],r=l(t),i=8*t.length,n=8*t.length;r[i>>>5]|=128<<24-i%32,r[14+(i+64>>>9<<4)]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8);for(var s=0;s>>24)|4278255360&(o<<24|o>>>8)}var a=y(e);return a}}(),function(){function e(){return new g(null)}function r(t,e,r,i,n,s){for(;--s>=0;){var o=e*this[t++]+r[i]+n;n=Math.floor(o/67108864),r[i++]=67108863&o}return n}function i(t,e,r,i,n,s){for(var o=32767&e,a=e>>15;--s>=0;){var u=32767&this[t],c=this[t++]>>15,h=a*u+c*o;u=o*u+((32767&h)<<15)+r[i]+(1073741823&n),n=(u>>>30)+(h>>>15)+a*c+(n>>>30),r[i++]=1073741823&u}return n}function n(t,e,r,i,n,s){for(var o=16383&e,a=e>>14;--s>=0;){var u=16383&this[t],c=this[t++]>>14,h=a*u+c*o;u=o*u+((16383&h)<<14)+r[i]+n,n=(u>>28)+(h>>14)+a*c,r[i++]=268435455&u}return n}function s(t){return T.charAt(t)}function o(t,e){var r=C[t.charCodeAt(e)];return null==r?-1:r}function a(t){var r=e();return r.fromInt(t),r}function u(t){var e,r=1;return 0!=(e=t>>>16)&&(t=e,r+=16),0!=(e=t>>8)&&(t=e,r+=8),0!=(e=t>>4)&&(t=e,r+=4),0!=(e=t>>2)&&(t=e,r+=2),0!=(e=t>>1)&&(t=e,r+=1),r}function c(t){if(0==t)return-1;var e=0;return 0==(65535&t)&&(t>>=16,e+=16),0==(255&t)&&(t>>=8,e+=8),0==(15&t)&&(t>>=4,e+=4),0==(3&t)&&(t>>=2,e+=2),0==(1&t)&&++e,e}function h(t){for(var e=0;0!=t;)t&=t-1,++e;return e}function f(t,e){return t&e}function p(t,e){return t|e}function l(t,e){return t^e}function y(t,e){return t&~e}var d,g=t.BigInteger=function t(e,r,i){if(!(this instanceof t))return new t(e,r,i);null!=e&&("number"==typeof e?this.fromNumber(e,r,i):null==r&&"string"!=typeof e?this.fromString(e,256):this.fromString(e,r))},v=0xdeadbeefcafe,m=15715070==(16777215&v);m&&"Microsoft Internet Explorer"==navigator.appName?(g.prototype.am=i,d=30):m&&"Netscape"!=navigator.appName?(g.prototype.am=r,d=26):(g.prototype.am=n,d=28),g.prototype.DB=d,g.prototype.DM=(1<=0;--e)t[e]=this[e];t.t=this.t,t.s=this.s},g.prototype.fromInt=function(t){this.t=1,this.s=t<0?-1:0,t>0?this[0]=t:t<-1?this[0]=t+this.DV:this.t=0},g.prototype.fromString=function(t,e){var r;if(16==e)r=4;else if(8==e)r=3;else if(256==e)r=8;else if(2==e)r=1;else if(32==e)r=5;else{if(4!=e)return void this.fromRadix(t,e);r=2}this.t=0,this.s=0;for(var i=t.length,n=!1,s=0;--i>=0;){var a=8==r?255&t[i]:o(t,i);a<0?"-"==t.charAt(i)&&(n=!0):(n=!1,0==s?this[this.t++]=a:s+r>this.DB?(this[this.t-1]|=(a&(1<>this.DB-s):this[this.t-1]|=a<=this.DB&&(s-=this.DB))}8==r&&0!=(128&t[0])&&(this.s=-1,s>0&&(this[this.t-1]|=(1<0&&this[this.t-1]==t;)--this.t},g.prototype.dlShiftTo=function(t,e){var r;for(r=this.t-1;r>=0;--r)e[r+t]=this[r];for(r=t-1;r>=0;--r)e[r]=0;e.t=this.t+t,e.s=this.s},g.prototype.drShiftTo=function(t,e){for(var r=t;r=0;--r)e[r+o+1]=this[r]>>n|a,a=(this[r]&s)<=0;--r)e[r]=0;e[o]=a,e.t=this.t+o+1,e.s=this.s,e.clamp()},g.prototype.rShiftTo=function(t,e){e.s=this.s;var r=Math.floor(t/this.DB);if(r>=this.t)e.t=0;else{var i=t%this.DB,n=this.DB-i,s=(1<>i;for(var o=r+1;o>i;i>0&&(e[this.t-r-1]|=(this.s&s)<>=this.DB;if(t.t>=this.DB;i+=this.s}else{for(i+=this.s;r>=this.DB;i-=t.s}e.s=i<0?-1:0,i<-1?e[r++]=this.DV+i:i>0&&(e[r++]=i),e.t=r,e.clamp()},g.prototype.multiplyTo=function(t,e){var r=this.abs(),i=t.abs(),n=r.t;for(e.t=n+i.t;--n>=0;)e[n]=0;for(n=0;n=0;)t[r]=0;for(r=0;r=e.DV&&(t[r+e.t]-=e.DV,t[r+e.t+1]=1)}t.t>0&&(t[t.t-1]+=e.am(r,e[r],t,2*r,0,1)),t.s=0,t.clamp()},g.prototype.divRemTo=function(t,r,i){var n=t.abs();if(!(n.t<=0)){var s=this.abs();if(s.t0?(n.lShiftTo(h,o),s.lShiftTo(h,i)):(n.copyTo(o),s.copyTo(i));var f=o.t,p=o[f-1];if(0!=p){var l=p*(1<1?o[f-2]>>this.F2:0),y=this.FV/l,d=(1<=0&&(i[i.t++]=1,i.subTo(B,i)),g.ONE.dlShiftTo(f,B),B.subTo(o,o);o.t=0;){var w=i[--m]==p?this.DM:Math.floor(i[m]*y+(i[m-1]+v)*d);if((i[m]+=o.am(0,w,i,b,0,f))0&&i.rShiftTo(h,i),a<0&&g.ZERO.subTo(i,i)}}},g.prototype.invDigit=function(){if(this.t<1)return 0;var t=this[0];if(0==(1&t))return 0;var e=3&t;return e=e*(2-(15&t)*e)&15,e=e*(2-(255&t)*e)&255,e=e*(2-((65535&t)*e&65535))&65535,e=e*(2-t*e%this.DV)%this.DV,e>0?this.DV-e:-e},g.prototype.isEven=function(){return 0==(this.t>0?1&this[0]:this.s)},g.prototype.exp=function(t,r){if(t>4294967295||t<1)return g.ONE;var i=e(),n=e(),s=r.convert(this),o=u(t)-1;for(s.copyTo(i);--o>=0;)if(r.sqrTo(i,n),(t&1<0)r.mulTo(n,s,i);else{var a=i;i=n,n=a}return r.revert(i)},g.prototype.toString=function(t){if(this.s<0)return"-"+this.negate().toString(t);var e;if(16==t)e=4;else if(8==t)e=3;else if(2==t)e=1;else if(32==t)e=5;else{if(4!=t)return this.toRadix(t);e=2}var r,i=(1<0)for(u>u)>0&&(n=!0,o=s(r));a>=0;)u>(u+=this.DB-e)):(r=this[a]>>(u-=e)&i,u<=0&&(u+=this.DB,--a)),r>0&&(n=!0),n&&(o+=s(r));return n?o:"0"},g.prototype.negate=function(){var t=e();return g.ZERO.subTo(this,t),t},g.prototype.abs=function(){return this.s<0?this.negate():this},g.prototype.compareTo=function(t){var e=this.s-t.s;if(0!=e)return e;var r=this.t;if(e=r-t.t,0!=e)return this.s<0?-e:e;for(;--r>=0;)if(0!=(e=this[r]-t[r]))return e;return 0},g.prototype.bitLength=function(){return this.t<=0?0:this.DB*(this.t-1)+u(this[this.t-1]^this.s&this.DM)},g.prototype.mod=function(t){var r=e();return this.abs().divRemTo(t,null,r),this.s<0&&r.compareTo(g.ZERO)>0&&t.subTo(r,r),r},g.prototype.modPowInt=function(t,e){var r;return r=t<256||e.isEven()?new I(e):new F(e),this.exp(t,r)},g.ZERO=a(0),g.ONE=a(1);var S=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],k=(1<<26)/S[S.length-1];g.prototype.chunkSize=function(t){return Math.floor(Math.LN2*this.DB/Math.log(t))},g.prototype.toRadix=function(t){if(null==t&&(t=10),0==this.signum()||t<2||t>36)return"0";var r=this.chunkSize(t),i=Math.pow(t,r),n=a(i),s=e(),o=e(),u="";for(this.divRemTo(n,s,o);s.signum()>0;)u=(i+o.intValue()).toString(t).substr(1)+u,s.divRemTo(n,s,o);return o.intValue().toString(t)+u},g.prototype.fromRadix=function(t,e){this.fromInt(0),null==e&&(e=10);for(var r=this.chunkSize(e),i=Math.pow(e,r),n=!1,s=0,a=0,u=0;u=r&&(this.dMultiply(i),this.dAddOffset(a,0),s=0,a=0))}s>0&&(this.dMultiply(Math.pow(e,s)),this.dAddOffset(a,0)),n&&g.ZERO.subTo(this,this)},g.prototype.fromNumber=function(t,e,r){if("number"==typeof e)if(t<2)this.fromInt(1);else for(this.fromNumber(t,r),this.testBit(t-1)||this.bitwiseTo(g.ONE.shiftLeft(t-1),p,this),this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(e);)this.dAddOffset(2,0),this.bitLength()>t&&this.subTo(g.ONE.shiftLeft(t-1),this);else{var i=new Array,n=7&t;i.length=1+(t>>3),e.nextBytes(i),n>0?i[0]&=(1<>=this.DB;if(t.t>=this.DB;i+=this.s}else{for(i+=this.s;r>=this.DB;i+=t.s}e.s=i<0?-1:0,i>0?e[r++]=i:i<-1&&(e[r++]=this.DV+i),e.t=r,e.clamp()},g.prototype.dMultiply=function(t){this[this.t]=this.am(0,t-1,this,0,0,this.t),++this.t,this.clamp()},g.prototype.dAddOffset=function(t,e){if(0!=t){for(;this.t<=e;)this[this.t++]=0;for(this[e]+=t;this[e]>=this.DV;)this[e]-=this.DV,++e>=this.t&&(this[this.t++]=0),++this[e]}},g.prototype.multiplyLowerTo=function(t,e,r){var i,n=Math.min(this.t+t.t,e);for(r.s=0,r.t=n;n>0;)r[--n]=0;for(i=r.t-this.t;n=0;)r[i]=0;for(i=Math.max(e-this.t,0);i0)if(0==e)r=this[0]%t;else for(var i=this.t-1;i>=0;--i)r=(e*r+this[i])%t;return r},g.prototype.millerRabin=function(t){var r=this.subtract(g.ONE),i=r.getLowestSetBit();if(i<=0)return!1;var n=r.shiftRight(i);t=t+1>>1,t>S.length&&(t=S.length);for(var s=e(),o=0;o>24},g.prototype.shortValue=function(){return 0==this.t?this.s:this[0]<<16>>16},g.prototype.signum=function(){return this.s<0?-1:this.t<=0||1==this.t&&this[0]<=0?0:1},g.prototype.toByteArray=function(){var t=this.t,e=new Array;e[0]=this.s;var r,i=this.DB-t*this.DB%8,n=0;if(t-- >0)for(i>i)!=(this.s&this.DM)>>i&&(e[n++]=r|this.s<=0;)i<8?(r=(this[t]&(1<>(i+=this.DB-8)):(r=this[t]>>(i-=8)&255,i<=0&&(i+=this.DB,--t)),0!=(128&r)&&(r|=-256),0==n&&(128&this.s)!=(128&r)&&++n,(n>0||r!=this.s)&&(e[n++]=r);return e},g.prototype.equals=function(t){return 0==this.compareTo(t)},g.prototype.min=function(t){return this.compareTo(t)<0?this:t},g.prototype.max=function(t){return this.compareTo(t)>0?this:t},g.prototype.and=function(t){var r=e();return this.bitwiseTo(t,f,r),r},g.prototype.or=function(t){var r=e();return this.bitwiseTo(t,p,r),r},g.prototype.xor=function(t){var r=e();return this.bitwiseTo(t,l,r),r},g.prototype.andNot=function(t){var r=e();return this.bitwiseTo(t,y,r),r},g.prototype.not=function(){for(var t=e(),r=0;r=this.t?0!=this.s:0!=(this[e]&1<1){var l=e();for(n.sqrTo(c[1],l);h<=p;)c[h]=e(),n.mulTo(l,c[h-2],c[h]),h+=2}var y,d,g=t.t-1,v=!0,m=e();for(s=u(t[g])-1;g>=0;){for(s>=f?y=t[g]>>s-f&p:(y=(t[g]&(1<0&&(y|=t[g-1]>>this.DB+s-f)),h=i;0==(1&y);)y>>=1,--h;if((s-=h)<0&&(s+=this.DB,--g),v)c[y].copyTo(o),v=!1;else{for(;h>1;)n.sqrTo(o,m),n.sqrTo(m,o),h-=2;h>0?n.sqrTo(o,m):(d=o,o=m,m=d),n.mulTo(m,c[y],o)}for(;g>=0&&0==(t[g]&1<=0?(r.subTo(i,r),e&&n.subTo(o,n),s.subTo(u,s)):(i.subTo(r,i),e&&o.subTo(n,o),u.subTo(s,u))}if(0!=i.compareTo(g.ONE))return g.ZERO;for(;u.compareTo(t)>=0;)u.subTo(t,u);for(;u.signum()<0;)u.addTo(t,u);return u},g.prototype.pow=function(t){return this.exp(t,new A)},g.prototype.gcd=function(t){var e=this.s<0?this.negate():this.clone(),r=t.s<0?t.negate():t.clone();if(e.compareTo(r)<0){var i=e;e=r,r=i}var n=e.getLowestSetBit(),s=r.getLowestSetBit();if(s<0)return e;for(n0&&(e.rShiftTo(s,e),r.rShiftTo(s,r));e.signum()>0;)(n=e.getLowestSetBit())>0&&e.rShiftTo(n,e),(n=r.getLowestSetBit())>0&&r.rShiftTo(n,r),e.compareTo(r)>=0?(e.subTo(r,e),e.rShiftTo(1,e)):(r.subTo(e,r),r.rShiftTo(1,r));return s>0&&r.lShiftTo(s,r),r},g.prototype.isProbablePrime=function(t){var e,r=this.abs();if(1==r.t&&r[0]<=S[S.length-1]){for(e=0;e=0?t.mod(this.m):t},I.prototype.revert=function(t){return t},I.prototype.reduce=function(t){t.divRemTo(this.m,null,t)},I.prototype.mulTo=function(t,e,r){t.multiplyTo(e,r),this.reduce(r)},I.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)};var F=t.Montgomery=function(t){this.m=t,this.mp=t.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<0&&this.m.subTo(r,r),r},F.prototype.revert=function(t){var r=e();return t.copyTo(r),this.reduce(r),r},F.prototype.reduce=function(t){for(;t.t<=this.mt2;)t[t.t++]=0;for(var e=0;e>15)*this.mpl&this.um)<<15)&t.DM;for(r=e+this.m.t,t[r]+=this.m.am(0,i,t,e,0,this.m.t);t[r]>=t.DV;)t[r]-=t.DV,t[++r]++}t.clamp(),t.drShiftTo(this.m.t,t),t.compareTo(this.m)>=0&&t.subTo(this.m,t)},F.prototype.mulTo=function(t,e,r){t.multiplyTo(e,r),this.reduce(r)},F.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)};var A=t.NullExp=function(){};A.prototype.convert=function(t){return t},A.prototype.revert=function(t){return t},A.prototype.mulTo=function(t,e,r){t.multiplyTo(e,r)},A.prototype.sqrTo=function(t,e){t.squareTo(e)};var x=t.Barrett=function(t){this.r2=e(),this.q3=e(),g.ONE.dlShiftTo(2*t.t,this.r2),this.mu=this.r2.divide(t),this.m=t};x.prototype.convert=function(t){if(t.s<0||t.t>2*this.m.t)return t.mod(this.m);if(t.compareTo(this.m)<0)return t;var r=e();return t.copyTo(r),this.reduce(r),r},x.prototype.revert=function(t){return t},x.prototype.reduce=function(t){for(t.drShiftTo(this.m.t-1,this.r2),t.t>this.m.t+1&&(t.t=this.m.t+1,t.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);t.compareTo(this.r2)<0;)t.dAddOffset(1,this.m.t+1);for(t.subTo(this.r2,t);t.compareTo(this.m)>=0;)t.subTo(this.m,t)},x.prototype.mulTo=function(t,e,r){t.multiplyTo(e,r),this.reduce(r)},x.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)}}(),f=t.EllipticCurve=function(){},f.FieldElementFp=function(t,e){this.x=e,this.q=t},f.FieldElementFp.prototype.equals=function(t){return t==this||this.q.equals(t.q)&&this.x.equals(t.x)},f.FieldElementFp.prototype.toBigInteger=function(){return this.x},f.FieldElementFp.prototype.negate=function(){return new f.FieldElementFp(this.q,this.x.negate().mod(this.q))},f.FieldElementFp.prototype.add=function(t){return new f.FieldElementFp(this.q,this.x.add(t.toBigInteger()).mod(this.q))},f.FieldElementFp.prototype.subtract=function(t){return new f.FieldElementFp(this.q,this.x.subtract(t.toBigInteger()).mod(this.q))},f.FieldElementFp.prototype.multiply=function(t){return new f.FieldElementFp(this.q,this.x.multiply(t.toBigInteger()).mod(this.q))},f.FieldElementFp.prototype.square=function(){return new f.FieldElementFp(this.q,this.x.square().mod(this.q))},f.FieldElementFp.prototype.divide=function(t){return new f.FieldElementFp(this.q,this.x.multiply(t.toBigInteger().modInverse(this.q)).mod(this.q))},f.FieldElementFp.prototype.getByteLength=function(){return Math.floor((this.toBigInteger().bitLength()+7)/8)},f.FieldElementFp.prototype.sqrt=function(){if(!this.q.testBit(0))throw new Error("even value of q");if(this.q.testBit(1)){var t=new f.FieldElementFp(this.q,this.x.modPow(this.q.shiftRight(2).add(BigInteger.ONE),this.q));return t.square().equals(this)?t:null}var e=this.q.subtract(BigInteger.ONE),r=e.shiftRight(1);if(!this.x.modPow(r,this.q).equals(BigInteger.ONE))return null;var i,n,s=e.shiftRight(2),o=s.shiftLeft(1).add(BigInteger.ONE),a=this.x,u=a.shiftLeft(2).mod(this.q);do{var c,h=new SecureRandom;do{c=new BigInteger(this.q.bitLength(),h)}while(c.compareTo(this.q)>=0||!c.multiply(c).subtract(u).modPow(r,this.q).equals(e));var p=f.FieldElementFp.fastLucasSequence(this.q,c,a,o);if(i=p[0],n=p[1],n.multiply(n).mod(this.q).equals(u))return n.testBit(0)&&(n=n.add(this.q)),n=n.shiftRight(1),new f.FieldElementFp(this.q,n)}while(i.equals(BigInteger.ONE)||i.equals(e));return null},function(t){function e(t,e){var r=4*t._blocksize,i=r-e.length%r;return i}var r=t.pad={},i=function(t,e,r,i){var n=e.pop();if(0==n)throw new Error("Invalid zero-length padding specified for "+r+". Wrong cipher specification or key used?");var s=4*t._blocksize;if(n>s)throw new Error("Invalid padding length of "+n+" specified for "+r+". Wrong cipher specification or key used?");for(var o=1;o0;i--)e.push(0)},unpad:function(t,e){for(;0==e[e.length-1];)e.pop()}},r.iso7816={pad:function(t,r){var i=e(t,r);for(r.push(128);i>1;i--)r.push(0)},unpad:function(t,e){var r;for(r=4*t._blocksize;r>0;r--){var i=e.pop();if(128==i)return -;if(0!=i)throw new Error("ISO-7816 padding byte must be 0, not 0x"+i.toString(16)+". Wrong cipher specification or key used?")}throw new Error("ISO-7816 padded beyond cipher block size. Wrong cipher specification or key used?")}},r.ansix923={pad:function(t,r){for(var i=e(t,r),n=1;n>>32-e}function o(t){var e,i=new Array(32),n=new Array(32);for(e=0;e<16;e++)i[e]=(255&t[4*e+0])<<0,i[e]|=(255&t[4*e+1])<<8,i[e]|=(255&t[4*e+2])<<16,i[e]|=(255&t[4*e+3])<<24;for(c(i,0,n,0,16),e=8;e>0;e-=2)n[4]^=r(n[0]+n[12],7),n[8]^=r(n[4]+n[0],9),n[12]^=r(n[8]+n[4],13),n[0]^=r(n[12]+n[8],18),n[9]^=r(n[5]+n[1],7),n[13]^=r(n[9]+n[5],9),n[1]^=r(n[13]+n[9],13),n[5]^=r(n[1]+n[13],18),n[14]^=r(n[10]+n[6],7),n[2]^=r(n[14]+n[10],9),n[6]^=r(n[2]+n[14],13),n[10]^=r(n[6]+n[2],18),n[3]^=r(n[15]+n[11],7),n[7]^=r(n[3]+n[15],9),n[11]^=r(n[7]+n[3],13),n[15]^=r(n[11]+n[7],18),n[1]^=r(n[0]+n[3],7),n[2]^=r(n[1]+n[0],9),n[3]^=r(n[2]+n[1],13),n[0]^=r(n[3]+n[2],18),n[6]^=r(n[5]+n[4],7),n[7]^=r(n[6]+n[5],9),n[4]^=r(n[7]+n[6],13),n[5]^=r(n[4]+n[7],18),n[11]^=r(n[10]+n[9],7),n[8]^=r(n[11]+n[10],9),n[9]^=r(n[8]+n[11],13),n[10]^=r(n[9]+n[8],18),n[12]^=r(n[15]+n[14],7),n[13]^=r(n[12]+n[15],9),n[14]^=r(n[13]+n[12],13),n[15]^=r(n[14]+n[13],18);for(e=0;e<16;++e)i[e]=n[e]+i[e];for(e=0;e<16;e++){var s=4*e;t[s+0]=i[e]>>0&255,t[s+1]=i[e]>>8&255,t[s+2]=i[e]>>16&255,t[s+3]=i[e]>>24&255}}function a(t,e,r,i,n){for(var s=n>>6;s--;)r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++],r[i++]^=t[e++]}function u(t,e,r){var i;return e+=64*(2*r-1),i=(255&t[e+0])<<0,i|=(255&t[e+1])<<8,i|=(255&t[e+2])<<16,i|=(255&t[e+3])<<24,i}function c(t,e,r,i,n){for(;n--;)r[i++]=t[e++]}function h(t,e,r,i,n){for(var s=n>>5;s--;)r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++],r[i++]=t[e++]}var f=[],l=[];if(void 0===p)onmessage=function(e){var r=e.data,i=r[0],n=r[1],s=(r[2],r[3]),o=r[4],a=[];h(s,128*o*n,a,0,128*n),t(a,0,n,i,l,f),postMessage([o,a])};else for(var y=0;y 0 and a power of 2");if(i>c/128/n)throw Error("Parameter N is too large");if(n>c/128/s)throw Error("Parameter r is too large");var f={iterations:1,hasher:Crypto.SHA256,asBytes:!0},p=Crypto.PBKDF2(e,r,128*s*n,f);try{var l=0,y=0,d=function(){if(!h){var r,c="("+u.toString()+")()";try{r=new Blob([c],{type:"text/javascript"})}catch(e){t.BlobBuilder=t.BlobBuilder||t.WebKitBlobBuilder||t.MozBlobBuilder||t.MSBlobBuilder,r=new BlobBuilder,r.append(c),r=r.getBlob("text/javascript")}h=URL.createObjectURL(r)}var d=new Worker(h);return d.onmessage=function(t){var r=t.data[0],u=t.data[1];y++,l1&&g[1].postMessage([i,n,s,p,l++])}catch(r){t.setTimeout(function(){u(),a(Crypto.PBKDF2(e,p,o,f))},0)}},function(){function t(t,e){for(var r=0,i=0;i<8;i++){1&e&&(r^=t);var n=128&t;t=t<<1&255,n&&(t^=27),e>>>=1}return r}for(var e=Crypto,r=e.util,i=e.charenc,n=i.UTF8,s=[99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22],o=[],a=0;a<256;a++)o[s[a]]=a;var u=[],c=[],h=[],f=[],p=[],l=[];for(a=0;a<256;a++)u[a]=t(a,2),c[a]=t(a,3),h[a]=t(a,9),f[a]=t(a,11),p[a]=t(a,13),l[a]=t(a,14);var y,d,g,v=[0,1,2,4,8,16,32,64,128,27,54],m=[[],[],[],[]],b=e.AES={encrypt:function(t,i,s){s=s||{};var o=s.mode||new e.mode.OFB;o.fixOptions&&o.fixOptions(s);var a=t.constructor==String?n.stringToBytes(t):t,u=s.iv||r.randomBytes(4*b._blocksize),c=i.constructor==String?e.PBKDF2(i,u,32,{asBytes:!0}):i;return b._init(c),o.encrypt(b,a,u),a=s.iv?a:u.concat(a),s&&s.asBytes?a:r.bytesToBase64(a)},decrypt:function(t,i,s){s=s||{};var o=s.mode||new e.mode.OFB;o.fixOptions&&o.fixOptions(s);var a=t.constructor==String?r.base64ToBytes(t):t,u=s.iv||a.splice(0,4*b._blocksize),c=i.constructor==String?e.PBKDF2(i,u,32,{asBytes:!0}):i;return b._init(c),o.decrypt(b,a,u),s&&s.asBytes?a:n.bytesToString(a)},_blocksize:4,_encryptblock:function(t,e){for(var r=0;r6&&e%y==4&&(r[0]=s[r[0]],r[1]=s[r[1]],r[2]=s[r[2]],r[3]=s[r[3]]),g[e]=[g[e-y][0]^r[0],g[e-y][1]^r[1],g[e-y][2]^r[2],g[e-y][3]^r[3]]}}}}(),f.FieldElementFp.fastLucasSequence=function(t,e,r,i){for(var n=i.bitLength(),s=i.getLowestSetBit(),o=BigInteger.ONE,a=BigInteger.TWO,u=e,c=BigInteger.ONE,h=BigInteger.ONE,f=n-1;f>=s+1;--f)c=c.multiply(h).mod(t),i.testBit(f)?(h=c.multiply(r).mod(t),o=o.multiply(u).mod(t),a=u.multiply(a).subtract(e.multiply(c)).mod(t),u=u.multiply(u).subtract(h.shiftLeft(1)).mod(t)):(h=c,o=o.multiply(a).subtract(c).mod(t),u=u.multiply(a).subtract(e.multiply(c)).mod(t),a=a.multiply(a).subtract(c.shiftLeft(1)).mod(t));for(c=c.multiply(h).mod(t),h=c.multiply(r).mod(t),o=o.multiply(a).subtract(c).mod(t),a=u.multiply(a).subtract(e.multiply(c)).mod(t),c=c.multiply(h).mod(t),f=1;f<=s;++f)o=o.multiply(a).mod(t),a=a.multiply(a).subtract(c.shiftLeft(1)).mod(t),c=c.multiply(c).mod(t);return[o,a]},f.PointFp=function(t,e,r,i,n){this.curve=t,this.x=e,this.y=r,this.z=null==i?BigInteger.ONE:i,this.zinv=null,this.compressed=!!n},f.PointFp.prototype.getX=function(){null==this.zinv&&(this.zinv=this.z.modInverse(this.curve.q));var t=this.x.toBigInteger().multiply(this.zinv);return this.curve.reduce(t),this.curve.fromBigInteger(t)},f.PointFp.prototype.getY=function(){null==this.zinv&&(this.zinv=this.z.modInverse(this.curve.q));var t=this.y.toBigInteger().multiply(this.zinv);return this.curve.reduce(t),this.curve.fromBigInteger(t)},f.PointFp.prototype.equals=function(t){return t==this||(this.isInfinity()?t.isInfinity():t.isInfinity()?this.isInfinity():(e=t.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(t.z)).mod(this.curve.q),!!e.equals(BigInteger.ZERO)&&(r=t.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(t.z)).mod(this.curve.q),r.equals(BigInteger.ZERO))));var e,r},f.PointFp.prototype.isInfinity=function(){return null==this.x&&null==this.y||this.z.equals(BigInteger.ZERO)&&!this.y.toBigInteger().equals(BigInteger.ZERO)},f.PointFp.prototype.negate=function(){return new f.PointFp(this.curve,this.x,this.y.negate(),this.z)},f.PointFp.prototype.add=function(t){if(this.isInfinity())return t;if(t.isInfinity())return this;var e=t.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(t.z)).mod(this.curve.q),r=t.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(t.z)).mod(this.curve.q);if(BigInteger.ZERO.equals(r))return BigInteger.ZERO.equals(e)?this.twice():this.curve.getInfinity();var i=new BigInteger("3"),n=this.x.toBigInteger(),s=this.y.toBigInteger(),o=(t.x.toBigInteger(),t.y.toBigInteger(),r.square()),a=o.multiply(r),u=n.multiply(o),c=e.square().multiply(this.z),h=c.subtract(u.shiftLeft(1)).multiply(t.z).subtract(a).multiply(r).mod(this.curve.q),p=u.multiply(i).multiply(e).subtract(s.multiply(a)).subtract(c.multiply(e)).multiply(t.z).add(e.multiply(a)).mod(this.curve.q),l=a.multiply(this.z).multiply(t.z).mod(this.curve.q);return new f.PointFp(this.curve,this.curve.fromBigInteger(h),this.curve.fromBigInteger(p),l)},f.PointFp.prototype.twice=function(){if(this.isInfinity())return this;if(0==this.y.toBigInteger().signum())return this.curve.getInfinity();var t=new BigInteger("3"),e=this.x.toBigInteger(),r=this.y.toBigInteger(),i=r.multiply(this.z),n=i.multiply(r).mod(this.curve.q),s=this.curve.a.toBigInteger(),o=e.square().multiply(t);BigInteger.ZERO.equals(s)||(o=o.add(this.z.square().multiply(s))),o=o.mod(this.curve.q);var a=o.square().subtract(e.shiftLeft(3).multiply(n)).shiftLeft(1).multiply(i).mod(this.curve.q),u=o.multiply(t).multiply(e).subtract(n.shiftLeft(1)).shiftLeft(2).multiply(n).subtract(o.square().multiply(o)).mod(this.curve.q),c=i.square().multiply(i).shiftLeft(3).mod(this.curve.q);return new f.PointFp(this.curve,this.curve.fromBigInteger(a),this.curve.fromBigInteger(u),c)},f.PointFp.prototype.multiply=function(t){if(this.isInfinity())return this;if(0==t.signum())return this.curve.getInfinity();var e,r=t,i=r.multiply(new BigInteger("3")),n=this.negate(),s=this;for(e=i.bitLength()-2;e>0;--e){s=s.twice();var o=i.testBit(e),a=r.testBit(e);o!=a&&(s=s.add(o?this:n))}return s},f.PointFp.prototype.multiplyTwo=function(t,e,r){var i;i=t.bitLength()>r.bitLength()?t.bitLength()-1:r.bitLength()-1;for(var n=this.curve.getInfinity(),s=this.add(e);i>=0;)n=n.twice(),t.testBit(i)?n=r.testBit(i)?n.add(s):n.add(this):r.testBit(i)&&(n=n.add(e)),--i;return n},f.PointFp.prototype.getEncoded=function(t){var e=this.getX().toBigInteger(),r=this.getY().toBigInteger(),i=32,n=f.integerToBytes(e,i);return t?r.isEven()?n.unshift(2):n.unshift(3):(n.unshift(4),n=n.concat(f.integerToBytes(r,i))),n},f.PointFp.decodeFrom=function(t,e){e[0];var r=e.length-1,i=e.slice(1,1+r/2),n=e.slice(1+r/2,1+r);i.unshift(0),n.unshift(0);var s=new BigInteger(i),o=new BigInteger(n);return new f.PointFp(t,t.fromBigInteger(s),t.fromBigInteger(o))},f.PointFp.prototype.add2D=function(t){if(this.isInfinity())return t;if(t.isInfinity())return this;if(this.x.equals(t.x))return this.y.equals(t.y)?this.twice():this.curve.getInfinity();var e=t.x.subtract(this.x),r=t.y.subtract(this.y),i=r.divide(e),n=i.square().subtract(this.x).subtract(t.x),s=i.multiply(this.x.subtract(n)).subtract(this.y);return new f.PointFp(this.curve,n,s)},f.PointFp.prototype.twice2D=function(){if(this.isInfinity())return this;if(0==this.y.toBigInteger().signum())return this.curve.getInfinity();var t=this.curve.fromBigInteger(BigInteger.valueOf(2)),e=this.curve.fromBigInteger(BigInteger.valueOf(3)),r=this.x.square().multiply(e).add(this.curve.a).divide(this.y.multiply(t)),i=r.square().subtract(this.x.multiply(t)),n=r.multiply(this.x.subtract(i)).subtract(this.y);return new f.PointFp(this.curve,i,n)},f.PointFp.prototype.multiply2D=function(t){if(this.isInfinity())return this;if(0==t.signum())return this.curve.getInfinity();var e,r=t,i=r.multiply(new BigInteger("3")),n=this.negate(),s=this;for(e=i.bitLength()-2;e>0;--e){s=s.twice();var o=i.testBit(e),a=r.testBit(e);o!=a&&(s=s.add2D(o?this:n))}return s},f.PointFp.prototype.isOnCurve=function(){var t=this.getX().toBigInteger(),e=this.getY().toBigInteger(),r=this.curve.getA().toBigInteger(),i=this.curve.getB().toBigInteger(),n=this.curve.getQ(),s=e.multiply(e).mod(n),o=t.multiply(t).multiply(t).add(r.multiply(t)).add(i).mod(n);return s.equals(o)},f.PointFp.prototype.toString=function(){return"("+this.getX().toBigInteger().toString()+","+this.getY().toBigInteger().toString()+")"},f.PointFp.prototype.validate=function(){var t=this.curve.getQ();if(this.isInfinity())throw new Error("Point is at infinity.");var e=this.getX().toBigInteger(),r=this.getY().toBigInteger();if(e.compareTo(BigInteger.ONE)<0||e.compareTo(t.subtract(BigInteger.ONE))>0)throw new Error("x coordinate out of bounds");if(r.compareTo(BigInteger.ONE)<0||r.compareTo(t.subtract(BigInteger.ONE))>0)throw new Error("y coordinate out of bounds");if(!this.isOnCurve())throw new Error("Point is not on the curve.");if(this.multiply(t).isInfinity())throw new Error("Point is not a scalar multiple of G.");return!0},f.CurveFp=function(t,e,r){this.q=t,this.a=this.fromBigInteger(e),this.b=this.fromBigInteger(r),this.infinity=new f.PointFp(this,null,null),this.reducer=new Barrett(this.q)},f.CurveFp.prototype.getQ=function(){return this.q},f.CurveFp.prototype.getA=function(){return this.a},f.CurveFp.prototype.getB=function(){return this.b},f.CurveFp.prototype.equals=function(t){return t==this||this.q.equals(t.q)&&this.a.equals(t.a)&&this.b.equals(t.b)},f.CurveFp.prototype.getInfinity=function(){return this.infinity},f.CurveFp.prototype.fromBigInteger=function(t){return new f.FieldElementFp(this.q,t)},f.CurveFp.prototype.reduce=function(t){this.reducer.reduce(t)},f.CurveFp.prototype.decodePointHex=function(t){var e=parseInt(t.substr(0,2),16);switch(e){case 0:return this.infinity;case 2:case 3:var r=1&e,i=t.substr(2,t.length-2),n=new BigInteger(i,16);return this.decompressPoint(r,n);case 4:case 6:case 7:var s=(t.length-2)/2,o=(i=t.substr(2,s),t.substr(s+2,s));return new f.PointFp(this,this.fromBigInteger(new BigInteger(i,16)),this.fromBigInteger(new BigInteger(o,16)));default:return null}},f.CurveFp.prototype.encodePointHex=function(t){if(t.isInfinity())return"00";var e=t.getX().toBigInteger().toString(16),r=t.getY().toBigInteger().toString(16),i=this.getQ().toString(16).length;for(i%2!=0&&i++;e.lengthr.length;)r.unshift(0);return r},f.X9Parameters=function(t,e,r,i){this.curve=t,this.g=e,this.n=r,this.h=i},f.X9Parameters.prototype.getCurve=function(){return this.curve},f.X9Parameters.prototype.getG=function(){return this.g},f.X9Parameters.prototype.getN=function(){return this.n},f.X9Parameters.prototype.getH=function(){return this.h},f.secNamedCurves={secp256k1:function(){var t=f.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"),e=BigInteger.ZERO,r=f.fromHex("7"),i=f.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),n=BigInteger.ONE,s=new f.CurveFp(t,e,r),o=s.decodePointHex("0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8");return new f.X9Parameters(s,o,i,n)}},f.getSECCurveByName=function(t){return null==f.secNamedCurves[t]?null:f.secNamedCurves[t]()},function(){function e(t){for(var e=[],r=0,i=t.length;r=33&&1==n[n.length-1]&&(n=n.slice(0,n.length-1),e=!0),{privkey:Crypto.util.bytesToHex(n),compressed:e}},r.wif2pubkey=function(t){var e=r.compressed,i=r.wif2privkey(t);r.compressed=i.compressed;var n=r.newPubkey(i.privkey);return r.compressed=e,{pubkey:n,compressed:i.compressed}},r.wif2address=function(t){var e=r.wif2pubkey(t);return{address:r.pubkey2address(e.pubkey),compressed:e.compressed}},r.newPubkey=function(t){var e=BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(t)),i=EllipticCurve.getSECCurveByName("secp256k1"),n=i.getG().multiply(e),s=n.getX().toBigInteger(),o=n.getY().toBigInteger(),a=EllipticCurve.integerToBytes(s,32);if(a=a.concat(EllipticCurve.integerToBytes(o,32)),a.unshift(4),1==r.compressed){var u=EllipticCurve.integerToBytes(s,32);return o.isEven()?u.unshift(2):u.unshift(3),Crypto.util.bytesToHex(u)}return Crypto.util.bytesToHex(a)},r.pubkey2address=function(t,e){var n=ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(t),{asBytes:!0}));n.unshift(e||r.pub);var s=Crypto.SHA256(Crypto.SHA256(n,{asBytes:!0}),{asBytes:!0}),o=s.slice(0,4);return i.encode(n.concat(o))},r.transaction=function(){var t={version:2,inputs:[],outputs:[],locktime:0,floData:"",addinput:function(e,r,i,n){var s={};return s.outpoint={hash:e,index:r},s.script=Crypto.util.hexToBytes(i),s.sequence=n||(0==t.locktime?4294967295:0),this.inputs.push(s)},addoutput:function(e,r){var i={},n=[],s=t.addressDecode(e);return i.value=new BigInteger(""+Math.round(1*r*1e8),10),n.push(118),n.push(169),n.push(s.length),n=n.concat(s),n.push(136),n.push(172),i.script=n,this.outputs.push(i)},addflodata:function(t){return this.floData=t,this.floData},addressDecode:function(t){var e=i.decode(t),r=e.slice(0,e.length-4),n=e.slice(e.length-4),s=Crypto.SHA256(Crypto.SHA256(r,{asBytes:!0}),{asBytes:!0}).slice(0,4);if(s+""==n+"")return r.slice(1)},transactionHash:function(t,e){for(var i=r.clone(this),n=e||1,s=0;s=128)if(i.inputs=[i.inputs[t]],129==n);else if(130==n)i.outputs=[];else if(131==n){i.outputs.length=t+1;for(s=0;s0&&(m=f.subtract(m));var B=s(v,m);return B.push(parseInt(o,10)),Crypto.util.bytesToHex(B)}return!1},deterministicK:function(t,e,i){i=i||0;var n=r.wif2privkey(t),s=Crypto.util.hexToBytes(n.privkey),o=EllipticCurve.getSECCurveByName("secp256k1"),a=o.getN(),u=[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],c=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];c=Crypto.HMAC(Crypto.SHA256,u.concat([0]).concat(s).concat(e),c,{asBytes:!0}),u=Crypto.HMAC(Crypto.SHA256,u,c,{asBytes:!0}),c=Crypto.HMAC(Crypto.SHA256,u.concat([1]).concat(s).concat(e),c,{asBytes:!0}),u=Crypto.HMAC(Crypto.SHA256,u,c,{asBytes:!0});var h=[];u=Crypto.HMAC(Crypto.SHA256,u,c,{asBytes:!0}),h=u;for(var f=BigInteger.fromByteArrayUnsigned(h),p=0;f.compareTo(a)>=0||f.compareTo(BigInteger.ZERO)<=0||p=0;){var n=e.mod(i.base);r.unshift(i.alphabet[n.intValue()]),e=e.subtract(n).divide(i.base)}r.unshift(i.alphabet[e.intValue()]);for(var s=0;s=0;n--){var s=i.alphabet.indexOf(t[n]);if(s<0)throw"Invalid character";e=e.add(BigInteger.valueOf(s).multiply(i.base.pow(t.length-1-n))),"1"==t[n]?r++:r=0}for(var o=e.toByteArrayUnsigned();r-- >0;)o.unshift(0);return o}}}(),y=t.Bitcoin={},d=y.Base58={alphabet:"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",validRegex:/^[1-9A-HJ-NP-Za-km-z]+$/,base:BigInteger.valueOf(58),encode:function(t){for(var e=BigInteger.fromByteArrayUnsigned(t),r=[];e.compareTo(d.base)>=0;){var i=e.mod(d.base);r.unshift(d.alphabet[i.intValue()]),e=e.subtract(i).divide(d.base)}r.unshift(d.alphabet[e.intValue()]);for(var n=0;n=0;i--){var n=d.alphabet.indexOf(t[i]);if(n<0)throw"Invalid character";e=e.add(BigInteger.valueOf(n).multiply(d.base.pow(t.length-1-i))),"1"==t[i]?r++:r=0}for(var s=e.toByteArrayUnsigned();r-- >0;)s.unshift(0);return s}},y.Address=function(e){"FLO"==t.cryptocoin?this.version=35:"FLO_TEST"==t.cryptocoin&&(this.version=115),"string"==typeof e&&(e=y.Address.decodeString(e,this.version)),this.hash=e},y.Address.networkVersion=35,y.Address.prototype.toString=function(t=null){var e=this.hash.slice(0);e.unshift(null!==t?t:this.version);var r=Crypto.SHA256(Crypto.SHA256(e,{asBytes:!0}),{asBytes:!0}),i=e.concat(r.slice(0,4));return y.Base58.encode(i)},y.Address.prototype.getHashBase64=function(){return Crypto.util.bytesToBase64(this.hash)},y.Address.decodeString=function(t,e){var r=y.Base58.decode(t),i=r.slice(0,21),n=Crypto.SHA256(Crypto.SHA256(i,{asBytes:!0}),{asBytes:!0});if(n[0]!=r[21]||n[1]!=r[22]||n[2]!=r[23]||n[3]!=r[24])throw"Checksum validation failed!";if(e!=i.shift())throw"Version "+i.shift()+" not supported!";return i},y.ECDSA=function(){function t(t,e,r,i){for(var n=Math.max(e.bitLength(),i.bitLength()),s=t.add2D(r),o=t.curve.getInfinity(),a=n-1;a>=0;--a)o=o.twice2D(),o.z=BigInteger.ONE,e.testBit(a)?o=i.testBit(a)?o.add2D(s):o.add2D(t):i.testBit(a)&&(o=o.add2D(r));return o}var e=EllipticCurve.getSECCurveByName("secp256k1"),r=new SecureRandom,i=null,n={getBigRandom:function(t){return new BigInteger(t.bitLength(),r).mod(t.subtract(BigInteger.ONE)).add(BigInteger.ONE)},sign:function(t,r){var i=r,s=e.getN(),o=BigInteger.fromByteArrayUnsigned(t);do{var a=n.getBigRandom(s),u=e.getG(),c=u.multiply(a),h=c.getX().toBigInteger().mod(s)}while(h.compareTo(BigInteger.ZERO)<=0);var f=a.modInverse(s).multiply(o.add(i.multiply(h))).mod(s);return n.serializeSig(h,f)},verify:function(t,r,i){var s,o,a;if(y.Util.isArray(r)){var u=n.parseSig(r);s=u.r,o=u.s}else{if("object"!=typeof r||!r.r||!r.s)throw"Invalid value for signature";s=r.r,o=r.s}if(i instanceof EllipticCurve.PointFp)a=i;else{if(!y.Util.isArray(i))throw"Invalid format for pubkey value, must be byte array or ec.PointFp";a=EllipticCurve.PointFp.decodeFrom(e.getCurve(),i)}var c=BigInteger.fromByteArrayUnsigned(t);return n.verifyRaw(c,s,o,a)},verifyRaw:function(t,r,i,n){var s=e.getN(),o=e.getG();if(r.compareTo(BigInteger.ONE)<0||r.compareTo(s)>=0)return!1;if(i.compareTo(BigInteger.ONE)<0||i.compareTo(s)>=0)return!1;var a=i.modInverse(s),u=t.multiply(a).mod(s),c=r.multiply(a).mod(s),h=o.multiply(u).add(n.multiply(c)),f=h.getX().toBigInteger().mod(s);return f.equals(r)},serializeSig:function(t,e){var r=t.toByteArraySigned(),i=e.toByteArraySigned(),n=[];return n.push(2),n.push(r.length),n=n.concat(r),n.push(2),n.push(i.length),n=n.concat(i),n.unshift(n.length), -n.unshift(48),n},parseSig:function(t){var e;if(48!=t[0])throw new Error("Signature not a valid DERSequence");if(e=2,2!=t[e])throw new Error("First element in signature must be a DERInteger");var r=t.slice(e+2,e+2+t[e+1]);if(e+=2+t[e+1],2!=t[e])throw new Error("Second element in signature must be a DERInteger");var i=t.slice(e+2,e+2+t[e+1]);e+=2+t[e+1];var n=BigInteger.fromByteArrayUnsigned(r),s=BigInteger.fromByteArrayUnsigned(i);return{r:n,s:s}},parseSigCompact:function(t){if(65!==t.length)throw"Signature has the wrong length";var r=t[0]-27;if(r<0||r>7)throw"Invalid signature type";var i=e.getN(),n=BigInteger.fromByteArrayUnsigned(t.slice(1,33)).mod(i),s=BigInteger.fromByteArrayUnsigned(t.slice(33,65)).mod(i);return{r:n,s:s,i:r}},recoverPubKey:function(r,s,o,a){a&=3;var u=1&a,c=a>>1,h=e.getN(),f=e.getG(),p=e.getCurve(),l=p.getQ(),d=p.getA().toBigInteger(),g=p.getB().toBigInteger();i||(i=l.add(BigInteger.ONE).divide(BigInteger.valueOf(4)));var v=c?r.add(h):r,m=v.multiply(v).multiply(v).add(d.multiply(v)).add(g).mod(l),b=m.modPow(i,l),B=(b.isEven(),(b.isEven()?!u:u)?b:l.subtract(b)),w=new EllipticCurve.PointFp(p,p.fromBigInteger(v),p.fromBigInteger(B));w.validate();var T=BigInteger.fromByteArrayUnsigned(o),C=BigInteger.ZERO.subtract(T).mod(h),S=r.modInverse(h),k=t(w,s,f,C).multiply(S);if(k.validate(),!n.verifyRaw(T,r,s,k))throw"Pubkey recovery unsuccessful";var I=new y.ECKey;return I.pub=k,I},calcPubkeyRecoveryParam:function(t,e,r,i){for(var n=0;n<4;n++)try{var s=y.ECDSA.recoverPubKey(e,r,i,n);if(s.getBitcoinAddress().toString()==t)return n}catch(t){}throw"Unable to find valid recovery factor"}};return n}(),y.KeyPool=(p=function(){return this.keyArray=[],this.push=function(t){if(null!=t&&null!=t.priv){var e=!0;for(var r in this.keyArray){var i=this.keyArray[r];if(null!=i&&null!=i.priv&&t.getBitcoinAddress()==i.getBitcoinAddress()){e=!1;break}}e&&this.keyArray.push(t)}},this.reset=function(){this.keyArray=[]},this.getArray=function(){return this.keyArray.slice(0)},this.setArray=function(t){this.keyArray=t},this.length=function(){return this.keyArray.length},this.toString=function(){var t="# = "+this.length()+"\n",e=this.getArray();for(var r in e){var i=e[r];y.Util.hasMethods(i,"getBitcoinAddress","toString")&&null!=i&&(t+='"'+i.getBitcoinAddress()+'", "'+i.toString("wif")+'"\n')}return t},this},new p),y.Bip38Key=(l=function(t,e){this.address=t,this.priv=e},l.prototype.getBitcoinAddress=function(){return this.address},l.prototype.toString=function(){return this.priv},l),y.ECKey=function(){var e=y.ECDSA,r=y.KeyPool,i=EllipticCurve.getSECCurveByName("secp256k1"),n=function(t){if(t){if(t instanceof BigInteger)this.priv=t;else if(y.Util.isArray(t))this.priv=BigInteger.fromByteArrayUnsigned(t);else if("string"==typeof t){var s=null;try{/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{52}$/.test(t)?(s=n.decodeCompressedWalletImportFormat(t),this.compressed=!0):n.isHexFormat(t)&&(s=Crypto.util.hexToBytes(t))}catch(t){this.setError(t)}n.isBase6Format(t)?this.priv=new BigInteger(t,6):null==s||32!=s.length?this.priv=null:this.priv=BigInteger.fromByteArrayUnsigned(s)}}else{var o=i.getN();this.priv=e.getBigRandom(o)}this.compressed=null==this.compressed?!!n.compressByDefault:this.compressed;try{null!=this.priv&&0==BigInteger.ZERO.compareTo(this.priv)&&this.setError("Error: BigInteger equal to zero.");var a="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140",u=Crypto.util.hexToBytes(a),c=BigInteger.fromByteArrayUnsigned(u);null!=this.priv&&c.compareTo(this.priv)<0&&this.setError("Error: BigInteger outside of curve range."),null!=this.priv&&r.push(this)}catch(t){this.setError(t)}};return"FLO"==t.cryptocoin?n.privateKeyPrefix=163:"FLO_TEST"==t.cryptocoin&&(n.privateKeyPrefix=239),n.compressByDefault=!1,n.prototype.setError=function(t){return this.error=t,this.priv=null,this},n.prototype.setCompressed=function(t){return this.compressed=!!t,this.pubPoint&&(this.pubPoint.compressed=this.compressed),this},n.prototype.getPub=function(){return this.compressed?this.pubComp?this.pubComp:this.pubComp=this.getPubPoint().getEncoded(1):this.pubUncomp?this.pubUncomp:this.pubUncomp=this.getPubPoint().getEncoded(0)},n.prototype.getPubPoint=function(){return this.pubPoint||(this.pubPoint=i.getG().multiply(this.priv),this.pubPoint.compressed=this.compressed),this.pubPoint},n.prototype.getPubKeyHex=function(){return this.compressed?this.pubKeyHexComp?this.pubKeyHexComp:this.pubKeyHexComp=Crypto.util.bytesToHex(this.getPub()).toString().toUpperCase():this.pubKeyHexUncomp?this.pubKeyHexUncomp:this.pubKeyHexUncomp=Crypto.util.bytesToHex(this.getPub()).toString().toUpperCase()},n.prototype.getPubKeyHash=function(){return this.compressed?this.pubKeyHashComp?this.pubKeyHashComp:this.pubKeyHashComp=y.Util.sha256ripe160(this.getPub()):this.pubKeyHashUncomp?this.pubKeyHashUncomp:this.pubKeyHashUncomp=y.Util.sha256ripe160(this.getPub())},n.prototype.getBitcoinAddress=function(){var t=this.getPubKeyHash(),e=new y.Address(t);return e.toString()},n.prototype.setPub=function(t){y.Util.isArray(t)&&(t=Crypto.util.bytesToHex(t).toString().toUpperCase());var e=i.getCurve().decodePointHex(t);return this.setCompressed(e.compressed),this.pubPoint=e,this},n.prototype.getBitcoinWalletImportFormat=function(){var t=this.getBitcoinPrivateKeyByteArray();if(null==t)return"";t.unshift(n.privateKeyPrefix),this.compressed&&t.push(1);var e=Crypto.SHA256(Crypto.SHA256(t,{asBytes:!0}),{asBytes:!0});t=t.concat(e.slice(0,4));var r=y.Base58.encode(t);return r},n.prototype.getBitcoinHexFormat=function(){return Crypto.util.bytesToHex(this.getBitcoinPrivateKeyByteArray()).toString().toUpperCase()},n.prototype.getBitcoinBase64Format=function(){return Crypto.util.bytesToBase64(this.getBitcoinPrivateKeyByteArray())},n.prototype.getBitcoinPrivateKeyByteArray=function(){if(null==this.priv)return null;for(var t=this.priv.toByteArrayUnsigned();t.length<32;)t.unshift(0);return t},n.prototype.toString=function(t){return t=t||"","base64"==t.toString().toLowerCase()||"b64"==t.toString().toLowerCase()?this.getBitcoinBase64Format():"wif"==t.toString().toLowerCase()?this.getBitcoinWalletImportFormat():this.getBitcoinHexFormat()},n.prototype.sign=function(t){return e.sign(t,this.priv)},n.prototype.verify=function(t,r){return e.verify(t,r,this.getPub())},n.decodeWalletImportFormat=function(t){var e=y.Base58.decode(t),r=e.slice(0,33),i=Crypto.SHA256(Crypto.SHA256(r,{asBytes:!0}),{asBytes:!0});if(i[0]!=e[33]||i[1]!=e[34]||i[2]!=e[35]||i[3]!=e[36])throw"Checksum validation failed!";return r.shift(),r},n.decodeCompressedWalletImportFormat=function(t){var e=y.Base58.decode(t),r=e.slice(0,34),i=Crypto.SHA256(Crypto.SHA256(r,{asBytes:!0}),{asBytes:!0});if(i[0]!=e[34]||i[1]!=e[35]||i[2]!=e[36]||i[3]!=e[37])throw"Checksum validation failed!";return r.shift(),r.pop(),r},n.isHexFormat=function(t){return t=t.toString(),/^[A-Fa-f0-9]{64}$/.test(t)},n.isWalletImportFormat=function(t){return t=t.toString(),128==n.privateKeyPrefix?/^5[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{50}$/.test(t):/^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{50}$/.test(t)},n.isCompressedWalletImportFormat=function(t){return t=t.toString(),128==n.privateKeyPrefix?/^[LK][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(t):/^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(t)},n.isBase64Format=function(t){return t=t.toString(),/^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+\/]{44}$/.test(t)},n.isBase6Format=function(t){return t=t.toString(),/^[012345]{99}$/.test(t)},n.isMiniFormat=function(t){t=t.toString();var e=/^S[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{21}$/.test(t),r=/^S[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{25}$/.test(t),i=/^S[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{29}$/.test(t),n=Crypto.SHA256(t+"?",{asBytes:!0});return(0===n[0]||1===n[0])&&(e||r||i)},n}(),y.Util={isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},makeFilledArray:function(t,e){for(var r=[],i=0;i>>8,255&t]:t<=1?[254].concat(Crypto.util.wordsToBytes([t])):[255].concat(Crypto.util.wordsToBytes([t>>>32,t]))},valueToBigInt:function(t){return t instanceof BigInteger?t:BigInteger.fromByteArrayUnsigned(t)},formatValue:function(t){for(var e=this.valueToBigInt(t).toString(),r=e.length>8?e.substr(0,e.length-8):"0",i=e.length>8?e.substr(e.length-8):e;i.length<8;)i="0"+i;for(i=i.replace(/0*$/,"");i.length<2;)i+="0";return r+"."+i},parseValue:function(t){for(var e=t.split("."),r=e[0],i=e[1]||"0";i.length<8;)i+="0";i=i.replace(/^0+/g,"");var n=BigInteger.valueOf(parseInt(r));return n=n.multiply(BigInteger.valueOf(1e8)),n=n.add(BigInteger.valueOf(parseInt(i))),n},sha256ripe160:function(t){return ripemd160(Crypto.SHA256(t,{asBytes:!0}),{asBytes:!0})},dsha256:function(t){return Crypto.SHA256(Crypto.SHA256(t,{asBytes:!0}),{asBytes:!0})},hasMethods:function(t){for(var e,r=1;e=arguments[r++];)if("function"!=typeof t[e])return!1;return!0}},g="secp256k1",m=t.ellipticCurveEncryption=function(){},m.rng=new SecureRandom,m.getCurveParameters=function(t){t=void 0!==t?t:"secp256k1";var e=EllipticCurve.getSECCurveByName(t),r={Q:"",A:"",B:"",GX:"",GY:"",N:""};return r.Q=e.getCurve().getQ().toString(),r.A=e.getCurve().getA().toBigInteger().toString(),r.B=e.getCurve().getB().toBigInteger().toString(),r.GX=e.getG().getX().toBigInteger().toString(),r.GY=e.getG().getY().toBigInteger().toString(),r.N=e.getN().toString(),r},m.selectedCurve=m.getCurveParameters(g),m.get_curve=function(){return new EllipticCurve.CurveFp(new BigInteger(this.selectedCurve.Q),new BigInteger(this.selectedCurve.A),new BigInteger(this.selectedCurve.B))},m.get_G=function(t){return new EllipticCurve.PointFp(t,t.fromBigInteger(new BigInteger(this.selectedCurve.GX)),t.fromBigInteger(new BigInteger(this.selectedCurve.GY)))},m.pick_rand=function(){var t=new BigInteger(this.selectedCurve.N),e=t.subtract(BigInteger.ONE),r=new BigInteger(t.bitLength(),this.rng);return r.mod(e).add(BigInteger.ONE)},m.senderRandom=function(){var t=this.pick_rand();return t.toString()},m.receiverRandom=function(){var t=this.pick_rand();return t.toString()},m.senderPublicString=function(t){var e={},r=this.get_curve(),i=this.get_G(r),n=new BigInteger(t),s=i.multiply(n);return e.XValuePublicString=s.getX().toBigInteger().toString(),e.YValuePublicString=s.getY().toBigInteger().toString(),e},m.receiverPublicString=function(t){var e={},r=this.get_curve(),i=this.get_G(r),n=new BigInteger(t),s=i.multiply(n);return e.XValuePublicString=s.getX().toBigInteger().toString(),e.YValuePublicString=s.getY().toBigInteger().toString(),e},m.senderSharedKeyDerivation=function(t,e,r){var i={},n=this.get_curve(),s=new EllipticCurve.PointFp(n,n.fromBigInteger(new BigInteger(t)),n.fromBigInteger(new BigInteger(e))),o=new BigInteger(r),a=s.multiply(o);return i.XValue=a.getX().toBigInteger().toString(),i.YValue=a.getY().toBigInteger().toString(),i},m.receiverSharedKeyDerivation=function(t,e,r){var i={},n=this.get_curve(),s=new EllipticCurve.PointFp(n,n.fromBigInteger(new BigInteger(t)),n.fromBigInteger(new BigInteger(e))),o=new BigInteger(r),a=s.multiply(o);return i.XValue=a.getX().toBigInteger().toString(),i.YValue=a.getY().toBigInteger().toString(),i},function(){function e(t){throw t}function r(t,e){this.a=t,this.b=e}function i(t,e){var r,i=[],n=(1<>>5]|=(t.charCodeAt(r/e)&n)<<32-e-r%32;return{value:i,binLen:s}}function n(t){var r,i,n=[],s=t.length;for(0!=s%2&&e("String of HEX type must be in byte increments"),r=0;r>>3]|=i<<24-r%8*4;return{value:n,binLen:4*s}}function s(t){var r,i,n,s,o,a=[],u=0;for(-1===t.search(/^[a-zA-Z0-9=+\/]+$/)&&e("Invalid character in base-64 string"),r=t.indexOf("="),t=t.replace(/\=/g,""),-1!==r&&r1&&"'"==s[s.length-1],a=2147483647&parseInt(o?s.slice(0,s.length-1):s);o&&(a+=2147483648),i=i.derive(a);var u=i.keys_extended.privkey&&""!=i.keys_extended.privkey?i.keys_extended.privkey:i.keys_extended.pubkey;i=r.hd(u)}return i},derive:function(t){t=t||0;var i,n,s,o,a=Crypto.util.hexToBytes(this.keys.pubkey).concat(r.numToBytes(t,4).reverse()),u=new jsSHA(Crypto.util.bytesToHex(a),"HEX"),c=u.getHMAC(Crypto.util.bytesToHex(e.chain_code),"HEX","SHA-512","HEX"),h=new BigInteger(c.slice(0,64),16),f=Crypto.util.hexToBytes(c.slice(64,128)),p=EllipticCurve.getSECCurveByName("secp256k1");p.getCurve();if(o=r.clone(this),o.chain_code=f,o.child_index=t,"private"==this.type)i=h.add(new BigInteger([0].concat(Crypto.util.hexToBytes(this.keys.privkey)))).mod(p.getN()),n=Crypto.util.bytesToHex(i.toByteArrayUnsigned()),s=r.newPubkey(n),o.keys={privkey:n,pubkey:s,wif:r.privkey2wif(n),address:r.pubkey2address(s)};else if("public"==this.type){q=p.curve.decodePointHex(this.keys.pubkey);var l=p.getG().multiply(h).add(q),y=l.getX().toBigInteger(),d=l.getY().toBigInteger(),g=EllipticCurve.integerToBytes(y,32);d.isEven()?g.unshift(2):g.unshift(3),s=Crypto.util.bytesToHex(g),o.keys={pubkey:s,address:r.pubkey2address(s)}}return o.parent_fingerprint=ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(e.keys.pubkey),{asBytes:!0}),{asBytes:!0}).slice(0,4),o.keys_extended=o.extend(),o},master:function(t){var e=t?Crypto.SHA256(t):r.newPrivkey(),i=new jsSHA(e,"HEX"),n=i.getHMAC("Bitcoin seed","TEXT","SHA-512","HEX"),s=(Crypto.util.hexToBytes(n.slice(0,64)),Crypto.util.hexToBytes(n.slice(64,128))),o=r.hd();return o.make({depth:0,parent_fingerprint:[0,0,0,0],child_index:0,chain_code:s,privkey:n.slice(0,64),pubkey:r.newPubkey(n.slice(0,64))})},make:function(t){var e=[];e.push(1*t.depth),e=e.concat(t.parent_fingerprint),e=e.concat(r.numToBytes(t.child_index,4).reverse()),e=e.concat(t.chain_code);var i={};if(t.privkey){var n=r.numToBytes(r.hdkey.prv,4).reverse();n=n.concat(e),n.push(0),n=n.concat(Crypto.util.hexToBytes(t.privkey));var s=Crypto.SHA256(Crypto.SHA256(n,{asBytes:!0}),{asBytes:!0}),o=s.slice(0,4),a=n.concat(o);i.privkey=r.base58encode(a)}if(t.pubkey){var u=r.numToBytes(r.hdkey.pub,4).reverse();u=u.concat(e),u=u.concat(Crypto.util.hexToBytes(t.pubkey));s=Crypto.SHA256(Crypto.SHA256(u,{asBytes:!0}),{asBytes:!0}),o=s.slice(0,4),a=u.concat(o);i.pubkey=r.base58encode(a)}return i}};return e.parse()},r.script=function(t){var e={};return t?"string"==typeof t?e.buffer=Crypto.util.hexToBytes(t):r.isArray(t)?e.buffer=t:t instanceof r.script?e.buffer=t.buffer:e.buffer=t:e.buffer=[],e.parse=function(){function t(t){r.chunks.push(r.buffer.slice(i,i+t)),i+=t}var r=this;e.chunks=[];for(var i=0;i=240&&(s=s<<8|this.buffer[i++]),s>0&&s<76?t(s):76==s?(n=this.buffer[i++],t(n)):77==s?(n=this.buffer[i++]<<8|this.buffer[i++],t(n)):78==s?(n=this.buffer[i++]<<24|this.buffer[i++]<<16|this.buffer[i++]<<8|this.buffer[i++],t(n)):this.chunks.push(s),i<0)break}return!0},e.decodeRedeemScript=function(t){var e=!1;try{var i=r.script(Crypto.util.hexToBytes(t));if(i.chunks.length>=3&&174==i.chunks[i.chunks.length-1]){e={},e.signaturesRequired=i.chunks[0]-80;for(var n=[],s=1;s>>8&255)):(this.buffer.push(78),this.buffer.push(255&t.length),this.buffer.push(t.length>>>8&255),this.buffer.push(t.length>>>16&255),this.buffer.push(t.length>>>24&255)),this.buffer=this.buffer.concat(t),this.chunks.push(t),!0},e.parse(),e},r.transaction=function(){var e={version:1,lock_time:0,ins:[],outs:[],witness:!1,timestamp:null,block:null,addinput:function(t,i,n,s){var o={};return o.outpoint={hash:t,index:i},o.script=r.script(n||[]),o.sequence=s||(0==e.lock_time?4294967295:0),this.ins.push(o)},addoutput:function(t,e){var i={};i.value=new BigInteger(""+Math.round(1*e*1e8),10);var n=r.script();return i.script=n.spendToScript(t),this.outs.push(i)},addstealth:function(t,e){var i=BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(r.newPrivkey())),n=EllipticCurve.getSECCurveByName("secp256k1"),s=EllipticCurve.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"),o=BigInteger.ZERO,a=EllipticCurve.fromHex("7"),u=new EllipticCurve.CurveFp(s,o,a),c=n.getG().multiply(i),h=u.decodePointHex(t.scankey),f=h.multiply(i),p=BigInteger.fromByteArrayUnsigned(Crypto.SHA256(f.getEncoded(!0),{asBytes:!0})),l=n.getG().multiply(p),y=u.decodePointHex(t.spendkey),d=y.add(l),g=r.pubkey2address(Crypto.util.bytesToHex(d.getEncoded(!0))),m=[6].concat(Crypto.util.randomBytes(4)).concat(c.getEncoded(!0)),b=r.script();b.writeOp(106),b.writeBytes(m),v={},v.value=0,v.script=b,this.outs.push(v);var B={};B.value=new BigInteger(""+Math.round(1*e*1e8),10);var w=r.script();return B.script=w.spendToScript(g),this.outs.push(B)},adddata:function(t){var e=!1;if(t.match(/^[a-f0-9]+$/gi)&&t.length<160&&t.length%2==0){var i=r.script();return i.writeOp(106),i.writeBytes(Crypto.util.hexToBytes(t)),o={},o.value=0,o.script=i,this.outs.push(o)}return e},listUnspent:function(t,e){r.ajax(r.host+"?uid="+r.uid+"&key="+r.key+"&setmodule=addresses&request=unspent&address="+t+"&r="+securedMathRandom(),e,"GET")},getTransaction:function(t,e){r.ajax(r.host+"?uid="+r.uid+"&key="+r.key+"&setmodule=bitcoin&request=gettransaction&txid="+t+"&r="+securedMathRandom(),e,"GET")},addUnspent:function(e,n,s,o,a){var u=this;this.listUnspent(e,function(e){var c=r.script(),h=0,f=0,p={};t.DOMParser?(parser=new DOMParser,xmlDoc=parser.parseFromString(e,"text/xml")):(xmlDoc=new ActiveXObject("Microsoft.XMLDOM"),xmlDoc.async=!1,xmlDoc.loadXML(e));var l=xmlDoc.getElementsByTagName("unspent")[0];if(l)for(i=1;i<=l.childElementCount;i++){var y=xmlDoc.getElementsByTagName("unspent_"+i)[0],d=y.getElementsByTagName("tx_hash")[0].childNodes[0].nodeValue.match(/.{1,2}/g).reverse().join("")+"",g=y.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue,v=s||y.getElementsByTagName("script")[0].childNodes[0].nodeValue;o&&(c=r.script(),c.writeBytes(Crypto.util.hexToBytes(s)),c.writeOp(0),c.writeBytes(r.numToBytes(1*y.getElementsByTagName("value")[0].childNodes[0].nodeValue,8)),v=Crypto.util.bytesToHex(c.buffer));var m=a||!1;u.addinput(d,g,v,m),h+=1*y.getElementsByTagName("value")[0].childNodes[0].nodeValue,f++}return p.result=xmlDoc.getElementsByTagName("result")[0].childNodes[0].nodeValue,p.unspent=l,p.value=h,p.total=f,p.response=xmlDoc.getElementsByTagName("response")[0].childNodes[0].nodeValue,n(p)})},addUnspentAndSign:function(t,e){var i=this,n=r.wif2address(t);i.addUnspent(n.address,function(r){return i.sign(t),e(r)})},broadcast:function(t,e){var i=e||this.serialize();r.ajax(r.host+"?uid="+r.uid+"&key="+r.key+"&setmodule=bitcoin&request=sendrawtransaction",t,"POST",["rawtx="+i])},transactionHash:function(t,e){for(var i=r.clone(this),n=e||1,s=0;s=128)if(i.ins=[i.ins[t]],129==n);else if(130==n)i.outs=[];else if(131==n){i.outs.length=t+1;for(s=0;s=80))for(var c=0;c=1?Crypto.SHA256(Crypto.SHA256(u,{asBytes:!0}),{asBytes:!0}):o;u=[];if(!(e>=80)&&2!=e&&3!=e)for(c=0;c=1?Crypto.SHA256(Crypto.SHA256(u,{asBytes:!0}),{asBytes:!0}):o,p=Crypto.util.hexToBytes(this.ins[t].outpoint.hash).reverse();p=p.concat(r.numToBytes(this.ins[t].outpoint.index,4));var l=r.numToBytes(this.ins[t].sequence,4),y=o;u=[];if(2!=e&&3!=e){for(c=0;c0&&(22==this.ins[t].script.chunks[0].length&&0==this.ins[t].script.chunks[0][0]||20==this.ins[t].script.chunks[0].length&&0==this.ins[t].script.chunks[1])){var e=this.witness[t]&&2==this.witness[t].length?"true":"false",i="true"==e?1:0,n=-1;return this.ins[t].script.chunks[2]&&8==this.ins[t].script.chunks[2].length&&(n=r.bytesToNum(this.ins[t].script.chunks[2])),{type:"segwit",signed:e,signatures:i,script:Crypto.util.bytesToHex(this.ins[t].script.chunks[0]),value:n}}if(0==this.ins[t].script.chunks[0]&&174==this.ins[t].script.chunks[this.ins[t].script.chunks.length-1][this.ins[t].script.chunks[this.ins[t].script.chunks.length-1].length-1]){var s=0;for(let e=1;e=80&&174==this.ins[t].script.chunks[this.ins[t].script.chunks.length-1])return{type:"multisig",signed:"false",signatures:0,script:Crypto.util.bytesToHex(this.ins[t].script.buffer)};if(0==this.ins[t].script.chunks.length){e=this.witness[t]&&2==this.witness[t].length?"true":"false",i="true"==e?1:0;return{type:"empty",signed:e,signatures:i,script:""}}return{type:"unknown",signed:"false",signatures:0,script:Crypto.util.bytesToHex(this.ins[t].script.buffer)}}return!1},transactionSig:function(t,e,i,n){function s(t,e){var r=t.toByteArraySigned(),i=e.toByteArraySigned(),n=[];return n.push(2),n.push(r.length),n=n.concat(r),n.push(2),n.push(i.length),n=n.concat(i),n.unshift(n.length),n.unshift(48),n}var o=i||1,a=n||Crypto.util.hexToBytes(this.transactionHash(t,o));if(a){var u=EllipticCurve.getSECCurveByName("secp256k1"),c=r.wif2privkey(e),h=BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(c.privkey)),f=u.getN(),p=BigInteger.fromByteArrayUnsigned(a),l=0;do{var y=this.deterministicK(e,a,l),d=u.getG(),g=d.multiply(y),v=g.getX().toBigInteger().mod(f),m=y.modInverse(f).multiply(p.add(h.multiply(v))).mod(f);l++}while(v.compareTo(BigInteger.ZERO)<=0||m.compareTo(BigInteger.ZERO)<=0);var b=f.shiftRight(1);m.compareTo(b)>0&&(m=f.subtract(m));var B=s(v,m);return B.push(parseInt(o,10)),Crypto.util.bytesToHex(B)}return!1},deterministicK:function(t,e,i){i=i||0;var n=r.wif2privkey(t),s=Crypto.util.hexToBytes(n.privkey),o=EllipticCurve.getSECCurveByName("secp256k1"),a=o.getN(),u=[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],c=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];c=Crypto.HMAC(Crypto.SHA256,u.concat([0]).concat(s).concat(e),c,{asBytes:!0}),u=Crypto.HMAC(Crypto.SHA256,u,c,{asBytes:!0}),c=Crypto.HMAC(Crypto.SHA256,u.concat([1]).concat(s).concat(e),c,{asBytes:!0}),u=Crypto.HMAC(Crypto.SHA256,u,c,{asBytes:!0});var h=[];u=Crypto.HMAC(Crypto.SHA256,u,c,{asBytes:!0}),h=u;for(var f=BigInteger.fromByteArrayUnsigned(h),p=0;f.compareTo(a)>=0||f.compareTo(BigInteger.ZERO)<=0||p=1)for(e=0;e=0)return!1;if(r.compareTo(BigInteger.ONE)<0||r.compareTo(s)>=0)return!1;var a=r.modInverse(s),u=t.multiply(a).mod(s),c=e.multiply(a).mod(s),h=o.multiply(u).add(i.multiply(c)),f=h.getX().toBigInteger().mod(s);return f.equals(e)},r.base58encode=function(t){for(var e="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",r=BigInteger.valueOf(58),i=BigInteger.fromByteArrayUnsigned(t),n=[];i.compareTo(r)>=0;){var s=i.mod(r);n.unshift(e[s.intValue()]),i=i.subtract(s).divide(r)}n.unshift(e[i.intValue()]);for(var o=0;o=0;s--){var o=e.indexOf(t[s]);if(o<0)throw"Invalid character";i=i.add(BigInteger.valueOf(o).multiply(r.pow(t.length-1-s))),"1"==t[s]?n++:n=0}for(var a=i.toByteArrayUnsigned();n-- >0;)a.unshift(0);return a},r.ajax=function(t,e,r,i){var n=!1;try{n=new ActiveXObject("Msxml2.XMLHTTP")}catch(t){try{n=new ActiveXObject("Microsoft.XMLHTTP")}catch(t){n=new XMLHttpRequest}}if(0==n)return!1;n.open(r,t,!0),n.onreadystatechange=function(){4==n.readyState&&e&&e(n.responseText)},"POST"==r&&n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.send(i)},r.clone=function(t){if(null==t||"object"!=typeof t)return t;var e=new t.constructor;for(var i in t)t.hasOwnProperty(i)&&(e[i]=r.clone(t[i]));return e},r.numToBytes=function(t,e){return void 0===e&&(e=8),0==e?[]:-1==t?Crypto.util.hexToBytes("ffffffffffffffff"):[t%256].concat(r.numToBytes(Math.floor(t/256),e-1))},r.numToScriptNumBytes=function(t){for(var r=Math.abs(t),i=e(r),n=[],s=0;s<|./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";for(let t=0;tg.maxBits))throw new Error("Number of bits must be an integer between "+g.minBits+" and "+g.maxBits+", inclusive.");v.radix=g.radix,v.bits=t||g.bits,v.size=Math.pow(2,v.bits),v.max=v.size-1;for(var e=[],r=[],i=1,n=g.primitivePolynomials[v.bits],s=0;s=v.size&&(i^=n,i&=v.max);v.logs=e,v.exps=r}function r(){return!!(v.bits&&v.size&&v.max&&v.logs&&v.exps&&v.logs.length===v.size&&v.exps.length===v.size)}function i(){function e(t,e,r,i){for(var n="",s=0,o=e.length-1;s=0;n--)i=0!==i?v.exps[(r+v.logs[i])%v.max]^e[n]:e[n];return i}function a(t,e){for(var r=0,i=t.length;rg.maxBits))throw new Error("Number of bits must be an integer between "+g.minBits+" and "+g.maxBits+", inclusive.");var r=Math.pow(2,e)-1,i=r.toString(v.radix).length,n=parseInt(t.substr(1,i),v.radix);if("number"!=typeof n||n%1!=0||n<1||n>r)throw new Error("Share id must be an integer between 1 and "+v.max+", inclusive.");if(t=t.substr(i+1),!t.length)throw new Error("Invalid share: zero-length share.");return{bits:e,id:n,value:t}}function c(t,r){for(var i,n,s=[],o=[],c="",d=0,g=r.length;dv.bits;i-=v.bits)r.push(parseInt(t.slice(i-v.bits,i),2));return r.push(parseInt(t.slice(0,i),2)),r}function p(t,e){e=e||v.bits;var r=t.length%e;return(r?new Array(e-r+1).join("0"):"")+t}function l(t){for(var e,r="",i=t.length-1;i>=0;i--){if(e=parseInt(t[i],16),isNaN(e))throw new Error("Invalid hex character.");r=p(e.toString(2),4)+r}return r}function y(t){var e,r="";t=p(t,4);for(var i=t.length;i>=4;i-=4){if(e=parseInt(t.slice(i-4,i),2),isNaN(e))throw new Error("Invalid binary character.");r=e.toString(16)+r}return r}var d=t.shamirSecretShare={},g={bits:8,radix:16,minBits:3,maxBits:20,bytesPerChar:2,maxBytesPerChar:6,primitivePolynomials:[null,null,1,3,3,5,3,3,29,17,9,5,83,27,43,3,45,9,39,39,9,5,3,33,27,9,71,39,9,5,83],warning:"WARNING:\nA secure random number generator was not found.\nUsing securedMathRandom(), which is NOT cryptographically strong!"},v={};d.getConfig=function(){return{bits:v.bits,unsafePRNG:v.unsafePRNG}},d.init=e,d.setRNG=function(t,e){if(r()||this.init(),v.unsafePRNG=!1,t=t||i(),"function"!=typeof t||"string"!=typeof t(v.bits)||!parseInt(t(v.bits),2)||t(v.bits).length>v.bits||t(v.bits).lengthv.max){var u=Math.ceil(Math.log(e+1)/Math.LN2);throw new Error("Number of shares must be an integer between 2 and 2^bits-1 ("+v.max+"), inclusive. To create "+e+" shares, use at least "+u+" bits.")}if("number"!=typeof i||i%1!=0||i<2)throw new Error("Threshold number of shares must be an integer between 2 and 2^bits-1 ("+v.max+"), inclusive.");if(i>v.max){u=Math.ceil(Math.log(i+1)/Math.LN2);throw new Error("Threshold number of shares must be an integer between 2 and 2^bits-1 ("+v.max+"), inclusive. To use a threshold of "+i+", use at least "+u+" bits.")}if("number"!=typeof o||o%1!=0)throw new Error("Zero-pad length must be an integer greater than 1.");v.unsafePRNG&&n(),t="1"+l(t),t=f(t,o);for(var c=new Array(e),h=new Array(e),d=0,g=t.length;di)throw new Error("Share id must be an integer between 1 and "+v.max+", inclusive.");var n=i.toString(v.radix).length;return v.bits.toString(36).toUpperCase()+p(t.toString(v.radix),n)+c(t,e)},d._lagrange=h,d.str2hex=function(t,e){if("string"!=typeof t)throw new Error("Input must be a character string.");if(e=e||g.bytesPerChar,"number"!=typeof e||e%1!=0||e<1||e>g.maxBytesPerChar)throw new Error("Bytes per character must be an integer between 1 and "+g.maxBytesPerChar+", inclusive.");for(var r,i=2*e,n=Math.pow(16,i)-1,s="",o=0,a=t.length;on){var u=Math.ceil(Math.log(r+1)/Math.log(256));throw new Error("Invalid character code ("+r+"). Maximum allowable is 256^bytes-1 ("+n+"). To convert this character, use at least "+u+" bytes.")}s=p(r.toString(16),i)+s}return s},d.hex2str=function(t,e){if("string"!=typeof t)throw new Error("Input must be a hexadecimal string.");if(e=e||g.bytesPerChar, -"number"!=typeof e||e%1!=0||e<1||e>g.maxBytesPerChar)throw new Error("Bytes per character must be an integer between 1 and "+g.maxBytesPerChar+", inclusive.");var r=2*e,i="";t=p(t,r);for(var n=0,s=t.length;ne.vectorClock?t:e},this.distance=function(t,e){let r=0,i=0;const n=Math.min(t.length,e.length),s=Math.max(t.length,e.length);for(;i=0?(this._update(r,i,t),this):r.contacts.length0&&r.length[this.distance(e.id,t),e]).sort((t,e)=>t[0]-e[0]).slice(0,e).map(t=>t[1])},this.count=function(){let t=0;for(const e=[this.root];e.length>0;){const r=e.pop();null===r.contacts?e.push(r.right,r.left):t+=r.contacts.length}return t},this._determineNode=function(t,e,r){const i=r>>3,n=r%8;if(e.length<=i&&0!==n)return t.left;const s=e[i];return s&1<<7-n?t.right:t.left},this.get=function(t){this.ensureInt8("id",t);let e=0,r=this.root;for(;null===r.contacts;)r=this._determineNode(r,t,e++);const i=this._indexOf(r,t);return i>=0?r.contacts[i]:null},this._indexOf=function(t,e){for(let r=0;r=0&&r.contacts.splice(i,1)[0],this},this._split=function(t,e){t.left=this.createNode(),t.right=this.createNode();for(const r of t.contacts)this._determineNode(t,r.id,e).contacts.push(r);t.contacts=null;const r=this._determineNode(t,this.localNodeId,e),i=t.left===r?t.right:t.left;i.dontSplit=!0},this.toArray=function(){let t=[];for(const e=[this.root];e.length>0;){const r=e.pop();null===r.contacts?e.push(r.right,r.left):t=t.concat(r.contacts)}return t},this._update=function(t,e,r){if(!this.arrayEquals(t.contacts[e].id,r.id))throw new Error("wrong index for _update");const i=t.contacts[e],n=this.arbiter(i,r);n===i&&i!==r||(t.contacts.splice(e,1),t.contacts.push(n))}}})("undefined"!=typeof global?global:window); \ No newline at end of file diff --git a/scripts/ribc.min.js b/scripts/ribc.min.js deleted file mode 100644 index 8d9099e..0000000 --- a/scripts/ribc.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){function e(e){if(a.projectBranches.hasOwnProperty(e))var t=a.projectBranches[e].split(",");else t=!1;return t}function t(e){for(var t={},n=a.projectBranches[e].split(","),r=0;r{Promise.all([n.refreshObjectData(),n.refreshGeneralData(e)]).then(e=>t(e)).catch(e=>r(e))})},n.refreshObjectData=(()=>new Promise((e,t)=>{floCloudAPI.requestObjectData("RIBC").then(t=>{floGlobals.appObjects.RIBC||(floGlobals.appObjects.RIBC={});var n=["projectMap","projectBranches","projectTaskDetails","projectDetails","internList","internRating","internRecord","internsAssigned","projectTaskStatus","displayedTasks"];n.forEach(e=>{floGlobals.appObjects.RIBC[e]||(floGlobals.appObjects.RIBC[e]={}),a[e]=floGlobals.appObjects.RIBC[e]}),e("Object Data Refreshed Successfully")}).catch(e=>t(e))})),n.refreshGeneralData=(e=>new Promise((t,n)=>{var r=["InternUpdates"],a=[],s=[];(e?r:s).push("TaskRequests","InternRequests");let o=[];for(let e of r)o.push(floCloudAPI.requestGeneralData(e));for(let e of a)o.push(floCloudAPI.requestGeneralData(e,{senderID:floGlobals.subAdmins}));for(let e of s)o.push(floCloudAPI.requestGeneralData(e,{senderID:floDapps.user.id}));Promise.all(o).then(e=>t("General Data Refreshed Successfully")).catch(e=>n(e))}));const a={};n.applyForIntern=(e=>new Promise((t,n)=>{floCloudAPI.sendGeneralData(e,"InternRequests").then(e=>t(e)).catch(e=>n(e))})),n.postInternUpdate=(e=>new Promise((t,n)=>{floCloudAPI.sendGeneralData(e,"InternUpdates").then(e=>t(e)).catch(e=>n(e))})),n.getInternUpdates=function(e=null){let t=Object.values(floGlobals.generalDataset("InternUpdates")).map(e=>({floID:e.senderID,update:e.message,time:e.vectorClock.split("_")[0],note:e.note,tag:e.tag}));return t=t.filter(e=>e.floID in a.internList),t.reverse(),e&&enew Promise((n,r)=>{if(!(e in floGlobals.generalDataset("InternUpdates")))return r("Intern update not found");floCloudAPI.noteApplicationData(e,t).then(e=>n(e)).catch(e=>r(e))})),n.applyForTask=(e=>new Promise((t,n)=>{floCloudAPI.sendGeneralData(e,"TaskRequests").then(e=>t(e)).catch(e=>n(e))})),n.getProjectList=(()=>Object.keys(a.projectMap)),n.getProjectDetails=(e=>a.projectDetails[e]),n.getProjectMap=(e=>a.projectMap[e]),n.getProjectBranches=(t=>e(t)),n.getTaskDetails=(e=>a.projectTaskDetails[e]),n.getTaskStatus=(e=>a.projectTaskStatus[e]),n.getInternList=(()=>a.internList),n.getInternRating=(e=>a.internRating[e]||0),n.getInternRecord=(e=>a.internRecord[e]||{}),n.getAssignedInterns=(e=>a.internsAssigned[e]||[]),n.getAllTasks=(()=>a.projectTaskDetails),n.getDisplayedTasks=(()=>floGlobals.appObjects.RIBC.displayedTasks||[]),r.updateObjects=(()=>new Promise((e,t)=>{floCloudAPI.updateObjectData("RIBC").then(t=>e(t)).catch(e=>t(e))})),r.resetObjects=(()=>new Promise((e,t)=>{floCloudAPI.resetObjectData("RIBC").then(t=>e(t)).catch(e=>t(e))})),r.addProjectDetails=function(e,t){if(!(e in a.projectMap))return"Project not Found!";if(e in a.projectDetails&&"object"==typeof e&&"object"==typeof t)for(let n in t)a.projectDetails[e][n]=t[n];else a.projectDetails[e]=t;return"added project details for "+e},n.getInternRequests=function(e=!0){var t=Object.values(floGlobals.generalDataset("InternRequests")).map(e=>({floID:e.senderID,vectorClock:e.vectorClock,details:e.message,status:e.note}));return t=t.filter(e=>!(e.floID in a.internList)),e&&(t=t.filter(e=>!e.status)),t},r.processInternRequest=function(e,t=!0){let n=floGlobals.generalDataset("InternRequests")[e];return n?(r=t&&s(n.senderID,n.message[0])?"Accepted":"Rejected",floCloudAPI.noteApplicationData(e,r).then(e=>null).catch(e=>console.error(e)),r):"Request not found";var r},r.initInternRecord=function(e){a.internRecord[e]||(a.internRecord[e]={active:!0,joined:Date.now(),assignedTasks:{},completedTasks:{},failedTasks:{}})};const s=r.addIntern=function(e,t){return!(e in a.internList)&&(a.internList[e]=t,a.internRating[e]=0,r.initInternRecord(e),!0)};r.renameIntern=function(e,t){return e in a.internList&&(a.internList[e]=t,!0)},r.removeIntern=function(e){if(!(e in a.internList))return!1;delete a.internList[e],delete a.internRating[e],delete a.internRecord[e];for(const t in a.projectTaskDetails)a.internsAssigned[t].includes(e)&&(a.internsAssigned[t]=a.internsAssigned[t].filter(t=>t!==e));return!0},r.addCompletedTask=function(e,t,n,s={}){if(!(e in a.internList))return!1;r.initInternRecord(e),a.internRecord[e].completedTasks[t]={points:n,...s};let o=0;for(const t in a.internRecord[e].completedTasks)o+=a.internRecord[e].completedTasks[t].points;const i=Object.keys(a.internRecord[e].completedTasks).length,c=Object.keys(a.internRecord[e].failedTasks).length;return a.internRating[e]=parseInt(o/(i+c)||1),!0},r.addFailedTask=function(e,t,n={}){return e in a.internList&&(r.initInternRecord(e),a.internRecord[e].failedTasks[t]={...n},r.unassignInternFromTask(e,t),!0)},r.setInternStatus=function(e,t=!0){return e in a.internList&&(a.internRecord[e].active=t,!0)},n.getTaskRequests=function(e=!0){var t=Object.values(floGlobals.generalDataset("TaskRequests")).map(e=>({floID:e.senderID,vectorClock:e.vectorClock,details:e.message,status:e.note}));try{floDapps.user.id&&!floGlobals.subAdmins.includes(floDapps.user.id)&&(t=t.filter(e=>e.floID===floDapps.user.id))}catch(e){return[]}return e&&(t=t.filter(e=>!e.status)),t},r.processTaskRequest=function(e,t=!0){let n=floGlobals.generalDataset("TaskRequests")[e];if(!n)return"Request not found";const{message:{taskId:r,name:a},senderID:i}=n,[c,l,p]=r.split("_");var d;return s(i,a),d=t&&o(i,c,l,p)?"Accepted":"Rejected",floCloudAPI.noteApplicationData(e,d)};const o=r.assignInternToTask=function(e,t,n,r){const s=t+"_"+n+"_"+r;return a.internsAssigned[s]||(a.internsAssigned[s]=[]),!a.internsAssigned[s].includes(e)&&(a.internsAssigned[s].push(e),a.internRecord[e].assignedTasks[s]={assignedOn:Date.now()},!0)};r.unassignInternFromTask=function(e,t){return!(!a.internsAssigned[t]||!a.internsAssigned[t].includes(e))&&(a.internsAssigned[t]=a.internsAssigned[t].filter(t=>t!==e),delete a.internRecord[e].assignedTasks[t],!0)},r.putTaskStatus=function(e,t,n,r){a.projectTaskStatus[t+"_"+n+"_"+r]=e},r.setDisplayedTasks=function(e=[]){floGlobals.appObjects.RIBC.displayedTasks=e},r.createProject=function(e){return e in a.projectMap?"Project Name already exists":(i(e),"Project Create: "+e)},r.copyBranchToNewProject=function(e,t,n,r,s,o){if("mainLine"==t)return"You cannot change mainLine";if(0==a.projectMap.hasOwnProperty(n))return"The project does not exist";if(0==a.projectMap[n].hasOwnProperty(c))return"The branch does not exist";if(s>o)return"Startpoint cannot be later than endpoint";var c=i(n,r,s,o);a.projectMap[n][c]=a.projectMap[e][t].slice(),a.projectMap[n][c][0]="undefined"==r?"mainLine":"newBranchConnection","undefined"!=s&&(a.projectMap[n][c][2]=s),"undefined"!=o&&(a.projectMap[n][c][3]=o);var l=a.projectTaskDetails;for(var p in l)if(l.hasOwnProperty(p)&&p.contains(e+"_"+t)){var d=p.replace(e+"_"+t+"_","");a.projectTaskDetails[n+"_"+c+"_"+d]=l[p]}return a.projectMap[n][c]},r.deleteTaskInMap=function(e,t,n){for(var r,s=a.projectMap[e][t],o=4;o4&&re!==t||"mainLine"!==e);for(o=0;o=c&&(c=i[p]+1),i[p]==r&&(l=p);return l>3?(i.splice(l+1,0,c),i[1]++,c):"Not possible to insert here.Try another position"},r.changeBranchLine=function(e,t,n,r,s){return"mainLine"==t?"You cannot change mainLine":0==a.projectMap.hasOwnProperty(e)?"The project does not exist":0==a.projectMap[e].hasOwnProperty(t)?"The branch does not exist":0==a.projectMap[e].hasOwnProperty(n)?"The newConnection does not exist":r>s?"Startpoint cannot be later than endpoint":(a.projectMap[e][t][0]=n,"undefined"!=r&&(a.projectMap[e][t][2]=r),"undefined"!=s&&(a.projectMap[e][t][3]=s),a.projectMap[e][t])},r.changeBranchPoint=function(e,t,n,r){var s;return"mainLine"!=t&&(1==r&&(n<=a.projectMap[e][t][3]?(a.projectMap[e][t][2]=n,s=n):s="Start point cannot be later than end point"),2==r&&(n>=a.projectMap[e][t][2]?(a.projectMap[e][t][3]=n,s=n):s="End point cannot be earlier than start point")),"mainLine"==t&&(s="mainLine cannot be rerouted"),s};const i=r.addBranch=function(t,n,r,s){var o,i=e(t);if(0==i)a.projectMap[t]={},a.projectMap[t].mainLine=["mainLine",0,"Start","Stop"],o="mainLine",a.projectBranches[t]="mainLine";else{var c=i[i.length-1];if(c.includes("branch")){var l=c.split("branch"),p=parseFloat(l[1])+1;o="branch"+p,a.projectMap[t]["branch"+p]=[n,0,r,s],a.projectBranches[t]=a.projectBranches[t]+",branch"+p}c.includes("mainLine")&&(o="branch1",a.projectMap[t].branch1=["mainLine",0,r,s],a.projectBranches[t]="mainLine,branch1")}return o};r.editTaskDetails=function(e,t,n,r){a.projectTaskDetails[t+"_"+n+"_"+r]={...a.projectTaskDetails[t+"_"+n+"_"+r],...e}},r.addTaskInMap=function(e,n){var r=[];r=t(e);var s=r[n],o=s+1;return a.projectMap[e][n].push(o),a.projectMap[e][n][1]++,o}})(); \ No newline at end of file