diff --git a/README.md b/README.md index adde843..415c6ec 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ $ npm install -g @theqrl/cli $ qrl-cli COMMAND running command... $ qrl-cli (-v|--version|version) -@theqrl/cli/1.4.2 darwin-x64 node-v12.3.1 +@theqrl/cli/1.4.5 darwin-x64 node-v12.3.1 $ qrl-cli --help [COMMAND] USAGE $ qrl-cli COMMAND @@ -77,7 +77,7 @@ DESCRIPTION See the documentation at https://docs.theqrl.org/developers/qrl-cli ``` -_See code: [src/commands/balance.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/balance.js)_ +_See code: [src/commands/balance.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/balance.js)_ ## `qrl-cli create-lattice` @@ -106,7 +106,7 @@ DESCRIPTION Documentation at https://docs.theqrl.org/developers/qrl-cli ``` -_See code: [src/commands/create-lattice.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/create-lattice.js)_ +_See code: [src/commands/create-lattice.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/create-lattice.js)_ ## `qrl-cli create-wallet` @@ -131,7 +131,7 @@ DESCRIPTION Documentation at https://docs.theqrl.org/developers/qrl-cli ``` -_See code: [src/commands/create-wallet.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/create-wallet.js)_ +_See code: [src/commands/create-wallet.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/create-wallet.js)_ ## `qrl-cli decrypt` @@ -147,7 +147,7 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/decrypt.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/decrypt.js)_ +_See code: [src/commands/decrypt.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/decrypt.js)_ ## `qrl-cli encrypt [ADDRESS] [ITEM_PER_PAGE] [PAGE_NUMBER] [MESSAGE]` @@ -171,7 +171,7 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/encrypt.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/encrypt.js)_ +_See code: [src/commands/encrypt.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/encrypt.js)_ ## `qrl-cli get-keys ADDRESS ITEM_PER_PAGE PAGE_NUMBER` @@ -193,7 +193,7 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/get-keys.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/get-keys.js)_ +_See code: [src/commands/get-keys.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/get-keys.js)_ ## `qrl-cli help [COMMAND]` @@ -237,7 +237,7 @@ DESCRIPTION If the wallet file is encrypted use the -p flag to pass the wallet file encryption password. ``` -_See code: [src/commands/ots.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/ots.js)_ +_See code: [src/commands/ots.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/ots.js)_ ## `qrl-cli receive ADDRESS` @@ -258,7 +258,7 @@ DESCRIPTION If using an encrypted wallet file pass the encryption password with the (-p) flag. ``` -_See code: [src/commands/receive.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/receive.js)_ +_See code: [src/commands/receive.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/receive.js)_ ## `qrl-cli receive-initial-message FILE` @@ -280,7 +280,7 @@ OPTIONS -t, --testnet uses testnet for this function ``` -_See code: [src/commands/receive-initial-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/receive-initial-message.js)_ +_See code: [src/commands/receive-initial-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/receive-initial-message.js)_ ## `qrl-cli receive-next-message INDEX` @@ -299,7 +299,7 @@ OPTIONS -t, --testnet uses testnet for this function ``` -_See code: [src/commands/receive-next-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/receive-next-message.js)_ +_See code: [src/commands/receive-next-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/receive-next-message.js)_ ## `qrl-cli send QUANTITY` @@ -329,7 +329,7 @@ DESCRIPTION TODO ``` -_See code: [src/commands/send.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/send.js)_ +_See code: [src/commands/send.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/send.js)_ ## `qrl-cli send-initial-message FILE` @@ -351,7 +351,7 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/send-initial-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/send-initial-message.js)_ +_See code: [src/commands/send-initial-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/send-initial-message.js)_ ## `qrl-cli send-next-message INDEX MESSAGE` @@ -371,7 +371,7 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/send-next-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/send-next-message.js)_ +_See code: [src/commands/send-next-message.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/send-next-message.js)_ ## `qrl-cli sign` @@ -390,7 +390,7 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/sign.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/sign.js)_ +_See code: [src/commands/sign.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/sign.js)_ ## `qrl-cli status` @@ -411,7 +411,7 @@ DESCRIPTION Advanced: you can use a custom defined node to query for status. Use the (-g) grpc endpoint. ``` -_See code: [src/commands/status.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/status.js)_ +_See code: [src/commands/status.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/status.js)_ ## `qrl-cli validate ADDRESS` @@ -432,7 +432,7 @@ DESCRIPTION when passed a QRL address in hexstring (preceded by 'Q'), will return details about the addresses validity. ``` -_See code: [src/commands/validate.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/validate.js)_ +_See code: [src/commands/validate.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/validate.js)_ ## `qrl-cli verify ADDRESS ITEM_PER_PAGE PAGE_NUMBER` @@ -453,5 +453,5 @@ OPTIONS -t, --testnet queries testnet for the OTS state ``` -_See code: [src/commands/verify.js](https://github.com/theqrl/qrl-cli/blob/v1.4.1/src/commands/verify.js)_ +_See code: [src/commands/verify.js](https://github.com/theqrl/qrl-cli/blob/v1.4.5/src/commands/verify.js)_ diff --git a/package.json b/package.json index df506d4..7390de7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@theqrl/cli", "description": "QRL CLI functions", - "version": "1.4.1", + "version": "1.4.5", "author": "JP Lomas , The QRL Foundation", "bin": { "qrl-cli": "./bin/run" @@ -13,6 +13,7 @@ "@oclif/command": "^1", "@oclif/config": "^1", "@oclif/plugin-help": "^2", + "@theqrl/explorer-helpers": "^0.2.5", "@theqrl/qrl-proto-sha256": "^2.0.3", "@theqrl/validate-qrl-address": "^1.1.0", "aes-js": "^3.1.2", diff --git a/src/commands/send.js b/src/commands/send.js index 80587ec..5e206a1 100644 --- a/src/commands/send.js +++ b/src/commands/send.js @@ -1,19 +1,102 @@ /* eslint new-cap: 0, max-depth: 0, complexity: 0 */ +/* global QRLLIB */ const {Command, flags} = require('@oclif/command') const {red, white} = require('kleur') -// const ora = require('ora') +const ora = require('ora') const fs = require('fs') const validateQrlAddress = require('@theqrl/validate-qrl-address') const aes256 = require('aes256') const {cli} = require('cli-ux') +const {QRLLIBmodule} = require('qrllib/build/offline-libjsqrl') // eslint-disable-line no-unused-vars +const {BigNumber} = require('bignumber.js') +const helpers = require('@theqrl/explorer-helpers') +let QRLLIBLoaded = false +let qrlClient = null +const waitForQRLLIB = callBack => { + setTimeout(() => { + // Test the QRLLIB object has the str2bin function. + // This is sufficient to tell us QRLLIB has loaded. + if (typeof QRLLIB.str2bin === 'function' && QRLLIBLoaded === true) { + callBack() + } else { + QRLLIBLoaded = true + return waitForQRLLIB(callBack) + } + return false + }, 50) +} -/* const {qrlClient, - checkProtoHash, +const {checkProtoHash, loadGrpcBaseProto, - loadGrpcProto} = require('../functions/grpc') */ + loadGrpcProto} = require('../functions/grpc') const shorPerQuanta = 10 ** 9 +const toUint8Vector = arr => { + const vec = new QRLLIB.Uint8Vector() + for (let i = 0; i < arr.length; i += 1) { + vec.push_back(arr[i]) + } + return vec +} + +// Convert bytes to hex +function bytesToHex(byteArray) { + return [...byteArray].map(function (byte) { + return ('00' + (byte & 0xFF).toString(16)).slice(-2) // eslint-disable-line no-bitwise + }).join('') +} + +// Concatenates multiple typed arrays into one. +function concatenateTypedArrays(resultConstructor, ...arrays) { + let totalLength = 0 + for (let arr of arrays) { + totalLength += arr.length + // console.log("TOTALLENGTH ", arr.length) + } + const result = new resultConstructor(totalLength) + let offset = 0 + for (let arr of arrays) { + result.set(arr, offset) + offset += arr.length + } + // console.log( "RESULT ", result) + return result +} + +// Take input and convert to unsigned uint64 bigendian bytes +function toBigendianUint64BytesUnsigned(input, bufferResponse = false) { + if (!Number.isInteger(input)) { + input = parseInt(input, 10) + } + + const byteArray = [0, 0, 0, 0, 0, 0, 0, 0] + + for (let index = 0; index < byteArray.length; index += 1) { + const byte = input & 0xFF // eslint-disable-line no-bitwise + byteArray[index] = byte + input = (input - byte) / 256 + } + + byteArray.reverse() + + if (bufferResponse === true) { + const result = Buffer.from(byteArray) + return result + } + const result = new Uint8Array(byteArray) + return result +} + +// Convert Binary object to Bytes +function binaryToBytes(convertMe) { + const thisBytes = new Uint8Array(convertMe.size()) + for (let i = 0; i < convertMe.size(); i += 1) { + thisBytes[i] = convertMe.get(i) + } + return thisBytes +} + const openWalletFile = function (path) { const contents = fs.readFileSync(path) return JSON.parse(contents)[0] @@ -129,12 +212,14 @@ class Send extends Command { shor: args.quantity, }) } else { + const convertAmountToBigNumber = new BigNumber(args.quantity) output.tx.push({ to: flags.recipient, - shor: args.quantity * shorPerQuanta, // going to need to do BigNumber here + shor: convertAmountToBigNumber.times(shorPerQuanta).toNumber(), }) } } + let hexseed = '' if (flags.wallet) { let isValidFile = false let address = '' @@ -143,6 +228,7 @@ class Send extends Command { if (walletJson.encrypted === false) { isValidFile = true address = walletJson.address + hexseed = walletJson.hexseed } if (walletJson.encrypted === true) { let password = '' @@ -152,6 +238,7 @@ class Send extends Command { password = await cli.prompt('Enter password for wallet file', {type: 'hide'}) } address = aes256.decrypt(password, walletJson.address) + hexseed = aes256.decrypt(password, walletJson.hexseed) if (validateQrlAddress.hexString(address).result) { isValidFile = true } else { @@ -171,11 +258,148 @@ class Send extends Command { // open from hexseed here if (flags.hexseed) { // reconstruct XMSS from hexseed + hexseed = flags.hexseed + } + let fee = 100 // default fee 100 Shor + if (flags.fee) { + const passedFee = parseInt(flags.fee, 10) + if (passedFee) { + fee = passedFee + } else { + this.log(`${red('⨉')} Fee is invalid`) + this.exit(1) + } } + const thisAddressesTo = [] + const thisAmounts = [] this.log('Transaction outputs:') output.tx.forEach(o => { this.log('address to: ' + o.to) this.log('amount in shor: ' + o.shor) + thisAddressesTo.push(helpers.hexAddressToRawAddress(o.to)) + thisAmounts.push(o.shor) + }) + this.log('Fee: ', fee) + const spinner = ora({text: 'Sending unsigned transaction to node...'}).start() + waitForQRLLIB(async _ => { + const XMSS_OBJECT = await new QRLLIB.Xmss.fromHexSeed(hexseed) + const xmssPK = Buffer.from(XMSS_OBJECT.getPK(), 'hex') + + // prepare transaction to send to node + const proto = await loadGrpcBaseProto(grpcEndpoint) + checkProtoHash(proto).then(async protoHash => { + if (!protoHash) { + this.log(`${red('⨉')} Unable to validate .proto file from node`) + this.exit(1) + } + // next load GRPC object and check hash of that too + qrlClient = await loadGrpcProto(proto, grpcEndpoint) + const request = { + addresses_to: thisAddressesTo, // eslint-disable-line camelcase + amounts: thisAmounts, + fee: fee, + xmss_pk: xmssPK, // eslint-disable-line camelcase + } + await qrlClient.transferCoins(request, async (error, response) => { + const tx = response + if (error) { + this.log(`${red('⨉')} Unable send transaction`) + this.exit(1) + } + spinner.succeed('Node correctly returned transaction for signing') + const spinner2 = ora({text: 'Signing transaction...'}).start() + + let concatenatedArrays = concatenateTypedArrays( + Uint8Array, + toBigendianUint64BytesUnsigned(tx.extended_transaction_unsigned.tx.fee) + ) + + // Now append all recipient (outputs) to concatenatedArrays + const addrsToRaw = tx.extended_transaction_unsigned.tx.transfer.addrs_to + const amountsRaw = tx.extended_transaction_unsigned.tx.transfer.amounts + const destAddr = [] + const destAmount = [] + for (let i = 0; i < addrsToRaw.length; i += 1) { + // Add address + concatenatedArrays = concatenateTypedArrays( + Uint8Array, + concatenatedArrays, + addrsToRaw[i] + ) + + // Add amount + concatenatedArrays = concatenateTypedArrays( + Uint8Array, + concatenatedArrays, + toBigendianUint64BytesUnsigned(amountsRaw[i]) + ) + + // Add to array for Ledger Transactions + destAddr.push(Buffer.from(addrsToRaw[i])) + destAmount.push(toBigendianUint64BytesUnsigned(amountsRaw[i], true)) + } + + // Convert Uint8Array to VectorUChar + const hashableBytes = toUint8Vector(concatenatedArrays) + + // Create sha256 sum of concatenatedarray + const shaSum = QRLLIB.sha2_256(hashableBytes) + + XMSS_OBJECT.setIndex(parseInt(flags.otsindex, 10)) + const signature = binaryToBytes(XMSS_OBJECT.sign(shaSum)) + // Calculate transaction hash + const txnHashConcat = concatenateTypedArrays( + Uint8Array, + binaryToBytes(shaSum), + signature, + xmssPK + ) + + const txnHashableBytes = toUint8Vector(txnHashConcat) + + const txnHash = QRLLIB.bin2hstr(QRLLIB.sha2_256(txnHashableBytes)) + + spinner2.succeed(`Transaction signed with OTS key ${flags.otsindex}. (nodes will reject this transaction if key reuse is detected)`) + const spinner3 = ora({text: 'Pushing transaction to node...'}).start() + + tx.extended_transaction_unsigned.tx.signature = Buffer.from(signature) + tx.extended_transaction_unsigned.tx.public_key = Buffer.from(xmssPK) // eslint-disable-line camelcase + + const addrsTo = tx.extended_transaction_unsigned.tx.transfer.addrs_to + const addrsToFormatted = [] + + addrsTo.forEach(item => { + const bufItem = Buffer.from(item) + addrsToFormatted.push(bufItem) + }) + tx.extended_transaction_unsigned.tx.transfer.addrs_to = addrsToFormatted // eslint-disable-line camelcase + + const pushTransactionReq = { + transaction_signed: tx.extended_transaction_unsigned.tx, // eslint-disable-line camelcase + } + await qrlClient.PushTransaction(pushTransactionReq, async (error, response) => { + if ((error || response.error_code) && response.error_code !== 'SUBMITTED') { + let errorMessage = 'unknown error' + if (response.error_code) { + errorMessage = `Unable send push transaction [error: ${response.error_description}` + } else { + errorMessage = `Node rejected signed message: has OTS key ${flags.otsindex} been reused?` + } + spinner3.fail(`${errorMessage}]`) + this.exit(1) + } + const pushTransactionRes = JSON.stringify(response.tx_hash) + const txhash = JSON.parse(pushTransactionRes) + if (txnHash === bytesToHex(txhash.data)) { + spinner3.succeed(`Transaction submitted to node: transaction ID: ${bytesToHex(txhash.data)}`) + this.exit(0) + } else { + spinner3.fail(`Node transaction hash ${bytesToHex(txhash.data)} does not match`) + this.exit(1) + } + }) + }) + }) }) } } @@ -201,7 +425,9 @@ Send.flags = { password: flags.string({char: 'p', required: false, description: 'wallet file password'}), shor: flags.boolean({char: 's', default: false, description: 'Send in Shor'}), jsonObject: flags.string({char: 'j', required: false, description: 'Pass a JSON object of recipients/quantities for multi-output transactions'}), - file: flags.string({char: 'f', required: false, description: 'JSON file of recipients'}), + fee: flags.string({char: 'f', required: false, description: 'Fee for transaction in Shor (defaults to 100 Shor)'}), + file: flags.string({char: 'r', required: false, description: 'JSON file of recipients'}), + otsindex: flags.string({char: 'i', required: true, description: 'OTS key index'}), wallet: flags.string({char: 'w', required: false, description: 'json file of wallet from where funds should be sent'}), hexseed: flags.string({char: 'h', required: false, description: 'hexseed/mnemonic of wallet from where funds should be sent'}), } diff --git a/yarn.lock b/yarn.lock index 88f99f9..99816de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -312,6 +312,17 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= +"@theqrl/explorer-helpers@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@theqrl/explorer-helpers/-/explorer-helpers-0.2.5.tgz#32c5598d6d8aaf7bf20d1a0119783e538923733d" + integrity sha512-K5xtI7jsSi3zYeJ+P+QADK3dSBToEhWfWrAL9JCl2JaugZ71huXbrRtuKg+2jaOq1MQa60kGEtsOHu1jyAU8mw== + dependencies: + axios "^0.18.0" + bech32 "^1.1.3" + mathjs "^4.1.1" + sha256 "^0.2.0" + underscore "^1.8.3" + "@theqrl/qrl-proto-sha256@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@theqrl/qrl-proto-sha256/-/qrl-proto-sha256-2.0.3.tgz#4fd72049a46ee4e9af4f088144e6466d49de9974" @@ -563,6 +574,14 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axios@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" + integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + axios@^0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" @@ -891,6 +910,11 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +complex.js@2.0.10: + version "2.0.10" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.10.tgz#afac74e4fb9131e8709f9c300420ce58fa36f18d" + integrity sha512-PsT3WqpnTjS2ijoMM8XodCi/BYO04vkS8kBg1YXcqf5KcnKVV6uXUc1eeLHhBksj8i7Vu9iQF2/6ZG9gqI6CPQ== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1008,6 +1032,11 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-9.0.1.tgz#1cc8b228177da7ab6498c1cc06eb130a290e6e1e" + integrity sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ== + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -1180,6 +1209,11 @@ es6-promise@4.2.8: resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== +escape-latex@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.0.3.tgz#1b9085e3e66570faabe21f05655643f64e78717a" + integrity sha512-GfKaG/7FOKdIdciylIzgaShBTPjdGQ5LJ2EcKLKXPLpcMO1MvCEVotkhydEShwCINRacZr2r3fk5A1PwZ4e5sA== + escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1590,6 +1624,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +fraction.js@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.8.tgz#e53618112e3b36b348c61a81323173bceb01e418" + integrity sha512-8Jx2AkFIFQtFaF8wP7yUIW+lnCgzPbxsholryMZ+oPK6kKjY/nUrvMKtq1+A8aSAeFau7+G/zfO8aGk2Aw1wCA== + from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -2190,6 +2229,11 @@ istanbul-reports@^2.1.1: dependencies: handlebars "^4.1.2" +javascript-natural-sort@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2431,6 +2475,20 @@ map-age-cleaner@^0.1.1: dependencies: p-defer "^1.0.0" +mathjs@^4.1.1: + version "4.4.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-4.4.2.tgz#79a5c8ad92a6434e22dbfac13e223a9bd62d6981" + integrity sha512-T5zGIbDT/JGmzIu2Bocq4U8gbcmQVCyZaJbBCHKmJkLMQoWuh1SOuFH98doj1JEQwjpKkq3rqdUCuy3vLlBZOA== + dependencies: + complex.js "2.0.10" + decimal.js "9.0.1" + escape-latex "1.0.3" + fraction.js "4.0.8" + javascript-natural-sort "0.7.1" + seed-random "2.2.0" + tiny-emitter "2.0.2" + typed-function "1.0.3" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -3289,6 +3347,11 @@ secp256k1@3.7.1: nan "^2.14.0" safe-buffer "^5.1.2" +seed-random@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" + integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= + "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" @@ -3637,6 +3700,11 @@ through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tiny-emitter@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" + integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -3715,6 +3783,11 @@ type-fest@^0.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== +typed-function@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-1.0.3.tgz#57026246214b664a5af45ef0a06679f4453bd090" + integrity sha512-sVC/1pm70oELDFMdYtFXMFqyawenLoaDiAXA3QvOAwKF/WvFNTSJN23cY2lFNL8iP0kh3T0PPKewrboO8XUVGQ== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -3735,6 +3808,11 @@ uid2@0.0.3: resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= +underscore@^1.8.3: + version "1.10.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" + integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== + unique-temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz#6dce95b2681ca003eebfb304a415f9cbabcc5385"