diff --git a/.gitignore b/.gitignore index 050a408..340e537 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ db-scripts/updates/ +db-scripts/1_db.sql +db-scripts/2_update.sql +docker/my-dojo/conf/docker-bitcoind.conf +docker/my-dojo/conf/docker-mysql.conf +docker/my-dojo/conf/docker-node.conf keys/index.js keys/sslcert/ node_modules/ private-tests/ *.log -package-lock.json diff --git a/README.md b/README.md index ea3d529..d5fd07c 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,10 @@ Authentication is enforced by an API key and Json Web Tokens. * Edit /keys/index.js and set "explorers.bitcoind" to "inactive". * Main drawbacks of using your local bitcoind for these imports: - * It doesn't return the full transactional history associated to the HD account but only transactions having an unspent output controlled by the HD account. - * It's slightly slower than using the option relying on the OXT API. - * In some specific cases, the importer might miss the most recent unspent outputs. Higher values of gap.external and gap.internal in /keys/index.js should help to mitigate this issue. Another workaround is to request the endpoint /support/xpub/.../rescan provided by the REST API with the optional gap parameter. * This option is considered as experimental. + * It doesn't return the full transactional history associated to an HD account or to an address but only transactions having an unspent output controlled by the HD account or the address. + * It's slightly slower than using the option relying on the OXT API. + * It may fail to correctly import an existing wallet if this wallet had a large activity. + * If you use bitcoind and if the import seems to return an invalid balance, you can use the "XPUB rescan" function provided by the maintenance tool. This function allows you to force the minimum number of addresses to be derived and the start index for the derivation. + * As a rule of thumb, we recommend to use bitcoind as the source of imports and to setup your Dojo with a new clean wallet. It increases your privacy and it removes all potential issues with the import of a large wallet. + diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..6ce777e --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,114 @@ +# Release Notes + + +## Samourai Dojo v1.1.0 ## + + +### Notable changes ### + + +#### Upgrade mechanism #### + +An upgrade mechanism for MyDojo has been added. + +See this [doc](./doc/DOCKER_setup.md#upgrade) for more details. + + +#### Optional support of an external bitcoin full node #### + +Optional support of an existing Bitcoin full node running outside of Docker has been added. + +This setup can be configured thanks to new options defined in ./docker/my-dojo/conf/docker-bitcoind.conf. When this option is activated, the install command skips the installation of bitcoind in Docker. + +Note: The Bitcoin full node installed by MyDojo is configured for taking care of your privacy at a network level. You may lose the benefits provided by the default setup if your external full node isn't properly configured. Use at your own risk. + +See this [doc](./doc/DOCKER_advanced_setups.md#external_bitcoind) for more details. + + +#### Optional support of external apps #### + +New options defined in ./docker/my-dojo/conf/docker-bitcoind.conf allow to expose the RPC API and ZMQ notifications provided by the full node of MyDojo to applications runnnig outside of Docker. + +Note: Exposing the full node of MyDojo to external applications may damage your privacy. Use at your own risk. + +See this [doc](./doc/DOCKER_advanced_setups.md#exposed_rpc_zmq) for more details. + + +#### Optional support of a static onion address for the full node #### + +A new option defined in ./docker/my-dojo/conf/docker-bitcoind.conf allows to keep a static onion address for your full node. + +By default, MyDojo generates a new onion address at each startup. We recommend to keep this default setup for better privacy. + +See this [doc](./doc/DOCKER_advanced_setups.md#static_onion) for more details. + + +#### Clean-up of Docker images #### + +A new "clean" command has been added to Dojo shell script for deleting old Docker images of MyDojo. + +This command allows to free disk space on the Docker host. + + +#### Documentation #### + +Added a new [doc](./doc/DOCKER_advanced_setups.md) for advanced setups. + +Added a new [doc](./doc/DOCKER_mac_setup.MD) for MacOS users. + + +### Change log ### + +#### MyDojo #### + +- [#1](https://github.com/Samourai-Wallet/samourai-dojo/pull/1) my-dojo upgrade mechanism +- [#7](https://github.com/Samourai-Wallet/samourai-dojo/pull/7) support of inbound connections through Tor +- [#8](https://github.com/Samourai-Wallet/samourai-dojo/pull/8) add config option exposing the rpc api and zmq notifications to external apps +- [#10](https://github.com/Samourai-Wallet/samourai-dojo/pull/10) add an option allowing to run dojo on top of an external bitcoind +- [#11](https://github.com/Samourai-Wallet/samourai-dojo/pull/11) clean-up +- [#12](https://github.com/Samourai-Wallet/samourai-dojo/pull/12) extend support of external apps +- [#15](https://github.com/Samourai-Wallet/samourai-dojo/pull/15) fix issue introduced by #10 +- [#19](https://github.com/Samourai-Wallet/samourai-dojo/pull/19) fix bitcoind port in torrc +- [#20](https://github.com/Samourai-Wallet/samourai-dojo/pull/20) increase nginx timeout +- [#25](https://github.com/Samourai-Wallet/samourai-dojo/pull/25) force the tracker to derive next indices if a hole is detected +- [#27](https://github.com/Samourai-Wallet/samourai-dojo/pull/27) rework external loop of Orchestrator +- [#28](https://github.com/Samourai-Wallet/samourai-dojo/pull/28) rework RemoteImporter +- [#32](https://github.com/Samourai-Wallet/samourai-dojo/pull/32) change the conditions switching the startup mode of the tracker +- [#33](https://github.com/Samourai-Wallet/samourai-dojo/pull/33) check authentication with admin key +- [#37](https://github.com/Samourai-Wallet/samourai-dojo/pull/37) automatic redirect of onion address to maintenance tool +- [#38](https://github.com/Samourai-Wallet/samourai-dojo/pull/38) dojo shutdown - replace sleep with static delay by docker wait + + +#### Security #### + +- [#5](https://github.com/Samourai-Wallet/samourai-dojo/pull/5) mydojo - install nodejs +- [#6](https://github.com/Samourai-Wallet/samourai-dojo/pull/6) remove deprecated "new Buffer" in favor of "Buffer.from" +- [#41](https://github.com/Samourai-Wallet/samourai-dojo/pull/41) update nodejs packages + + +#### Documentation #### + +- [#13](https://github.com/Samourai-Wallet/samourai-dojo/pull/13) included Mac instructions +- [92097d8](https://github.com/Samourai-Wallet/samourai-dojo/commit/92097d8ec7f9488ce0318c452356994315f4be72) doc +- [de4c9b5](https://github.com/Samourai-Wallet/samourai-dojo/commit/de4c9b5e5078b673c7b199503d48e7ceca328285) doc - minor updates +- [fead0bb](https://github.com/Samourai-Wallet/samourai-dojo/commit/fead0bb4b2b6174e637f5cb8c57edd9b55c3a1c7) doc - add link to MacOS install doc +- [#42](https://github.com/Samourai-Wallet/samourai-dojo/pull/42) fix few typos, add backticks for config values +- [#43](https://github.com/Samourai-Wallet/samourai-dojo/pull/43) add missing `d` in `docker-bitcoind.conf` + + +#### Misc #### + +- [a382e42](https://github.com/Samourai-Wallet/samourai-dojo/commit/a382e42469b884d2eda9fa6f5a3c8ce93a7cd39a) add sql scripts and config files to gitignore + + +### Credits ### + +- 05nelsonm +- clarkmoody +- dergigi +- hkjn +- kenshin-samourai +- LaurentMT +- michel-foucault +- pxsocs +- Technifocal diff --git a/accounts/api-helper.js b/accounts/api-helper.js index b39f561..e0c4d2b 100644 --- a/accounts/api-helper.js +++ b/accounts/api-helper.js @@ -28,10 +28,10 @@ class ApiHelper { */ parseEntities(str) { const ret = new WalletEntities() - + if (typeof str !== 'string') return ret - + for (let item of str.split('|')) { try { @@ -47,7 +47,7 @@ class ApiHelper { } else if (addrHelper.isSupportedPubKey(item) && !ret.hasPubKey(item)) { // Derive pubkey as 3 addresses (P1PKH, P2WPKH/P2SH, BECH32) - const bufItem = new Buffer(item, 'hex') + const bufItem = Buffer.from(item, 'hex') const funcs = [ addrHelper.p2pkhAddress, diff --git a/db-scripts/1_db.sql b/db-scripts/1_db.sql.tpl similarity index 93% rename from db-scripts/1_db.sql rename to db-scripts/1_db.sql.tpl index 0095fc5..290d520 100644 --- a/db-scripts/1_db.sql +++ b/db-scripts/1_db.sql.tpl @@ -25,10 +25,9 @@ -- Table structure for table `addresses` -- -DROP TABLE IF EXISTS `addresses`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `addresses` ( +CREATE TABLE IF NOT EXISTS `addresses` ( `addrID` int(10) unsigned NOT NULL AUTO_INCREMENT, `addrAddress` varchar(74) DEFAULT NULL, PRIMARY KEY (`addrID`), @@ -40,10 +39,9 @@ CREATE TABLE `addresses` ( -- Table structure for table `banned_addresses` -- -DROP TABLE IF EXISTS `banned_addresses`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `banned_addresses` ( +CREATE TABLE IF NOT EXISTS `banned_addresses` ( `bannedAddressId` int(11) NOT NULL AUTO_INCREMENT, `addrAddress` varchar(35) NOT NULL, PRIMARY KEY (`bannedAddressId`), @@ -55,10 +53,9 @@ CREATE TABLE `banned_addresses` ( -- Table structure for table `blocks` -- -DROP TABLE IF EXISTS `blocks`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `blocks` ( +CREATE TABLE IF NOT EXISTS `blocks` ( `blockID` int(10) unsigned NOT NULL AUTO_INCREMENT, `blockHash` char(64) NOT NULL DEFAULT '', `blockParent` int(10) unsigned DEFAULT NULL, @@ -76,10 +73,9 @@ CREATE TABLE `blocks` ( -- Table structure for table `hd` -- -DROP TABLE IF EXISTS `hd`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `hd` ( +CREATE TABLE IF NOT EXISTS `hd` ( `hdID` int(10) unsigned NOT NULL AUTO_INCREMENT, `hdXpub` char(112) DEFAULT NULL, `hdCreated` int(10) unsigned NOT NULL DEFAULT '0', @@ -94,10 +90,9 @@ CREATE TABLE `hd` ( -- Table structure for table `hd_addresses` -- -DROP TABLE IF EXISTS `hd_addresses`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `hd_addresses` ( +CREATE TABLE IF NOT EXISTS `hd_addresses` ( `hdAddrID` int(10) unsigned NOT NULL AUTO_INCREMENT, `hdID` int(10) unsigned NOT NULL DEFAULT '0', `addrID` int(10) unsigned NOT NULL DEFAULT '0', @@ -116,10 +111,9 @@ CREATE TABLE `hd_addresses` ( -- Table structure for table `inputs` -- -DROP TABLE IF EXISTS `inputs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `inputs` ( +CREATE TABLE IF NOT EXISTS `inputs` ( `inID` int(10) unsigned NOT NULL AUTO_INCREMENT, `outID` int(10) unsigned NOT NULL DEFAULT '0', `txnID` int(10) unsigned NOT NULL DEFAULT '0', @@ -138,10 +132,9 @@ CREATE TABLE `inputs` ( -- Table structure for table `outputs` -- -DROP TABLE IF EXISTS `outputs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `outputs` ( +CREATE TABLE IF NOT EXISTS `outputs` ( `outID` int(10) unsigned NOT NULL AUTO_INCREMENT, `txnID` int(10) unsigned NOT NULL DEFAULT '0', `addrID` int(10) unsigned NOT NULL DEFAULT '0', @@ -161,10 +154,9 @@ CREATE TABLE `outputs` ( -- Table structure for table `transactions` -- -DROP TABLE IF EXISTS `transactions`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `transactions` ( +CREATE TABLE IF NOT EXISTS `transactions` ( `txnID` int(10) unsigned NOT NULL AUTO_INCREMENT, `txnTxid` char(64) DEFAULT NULL, `txnCreated` int(10) unsigned NOT NULL DEFAULT '0', @@ -183,10 +175,9 @@ CREATE TABLE `transactions` ( -- Table structure for table `scheduled_transactions` -- -DROP TABLE IF EXISTS `scheduled_transactions`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `scheduled_transactions` ( +CREATE TABLE IF NOT EXISTS `scheduled_transactions` ( `schID` int(10) unsigned NOT NULL AUTO_INCREMENT, `schTxid` char(64) NOT NULL DEFAULT '', `schCreated` int(10) unsigned NOT NULL DEFAULT '0', diff --git a/db-scripts/2_update.sql.tpl b/db-scripts/2_update.sql.tpl new file mode 100644 index 0000000..1aba698 --- /dev/null +++ b/db-scripts/2_update.sql.tpl @@ -0,0 +1,7 @@ +# SQL scripts for incremental updates + +# Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved. + +-- +-- UPDATES vX.Y.Z +-- diff --git a/doc/DOCKER_advanced_setups.md b/doc/DOCKER_advanced_setups.md new file mode 100644 index 0000000..9eaec36 --- /dev/null +++ b/doc/DOCKER_advanced_setups.md @@ -0,0 +1,161 @@ +# MyDojo - Advanced Setups + + +The 3 configuration files of Dojo provide a few advanced options allowing to tune your setup. + +A word of caution, though, the default values of these options try to maximize your privacy at a network level. All the advanced setups described in this document may damage your privacy. Use at your own risk! + + + + +## External Bitcoin full node ## + +By default, Dojo installs and runs a Bitcoin full node in Docker. + +The following procedure allows to bypass the installation of this full node by telling Dojo to rely on an external bitcoind running on your host machine. + + +### Requirements ### + +The external full node mustn't be pruned. + +The external full node must be configured for the support of Dojo. Edit the bitcoin.conf file of your external full node and check that the following lines are properly initialized. + +``` +# Force bitcoind to accept JSON-RPC commands +server=1 + +# Force bitcoind to index all the transactions +txindex=1 + +# Check that bitcoind accepts connections from 127.0.0.1 (linux) +# or from the IP address of the Docker Virtual Machine (MacOS, Windows) +rpcallowip=... + +# Check that a port is defined for the RPC API (or 8332 will be used as default value) +rpcport=... + +# Check that the RPC API listens on an IP address accessible from the nodejs container +rpcbind=... + +# Check that the RPC user is set +rpcuser=... + +# Check that the RPC password is set +rpcpassword= + +# Enable publish hash block on an IP address accessible from the nodejs container +zmqpubhashblock=... + +# Enable publish raw transaction on an IP address accessible from the nodejs container +zmqpubrawtx=... +``` + + +### Procedure ### + +#### Configuration of Dojo #### + +``` +# Edit the bitcoin config template file +nano ./conf/docker-bitcoind.conf.tpl + +# +# Set the value of BITCOIND_INSTALL to "off" +# Set the value of BITCOIND_IP with the IP address of you bitcoin full node +# Set the value of BITCOIND_RPC_PORT with the port used by your bitcoin full node for the RPC API +# Set the value of BITCOIND_ZMQ_RAWTXS with the port used by your bitcoin full node for ZMQ notifications of raw transactions +# (i.e. port defined for -zmqpubrawtx in the bitcoin.conf of your full node) +# Set the value of BITCOIND_ZMQ_BLK_HASH with the port used by your bitcoin full node for ZMQ notifications of block hashes +# (i.e. port defined for -zmqpubhashblock in the bitcoin.conf of your full node) +# +# Save and exit nano +# +``` + +### Fast import of block headers in Dojo (optional) ### + +When Dojo is installed for the first time, the Tracker imports the block headers in the database. + +Follow these steps if you want to speed up this operation by preloading an archive of the block headers. + +``` +# Download the archive [https://samouraiwallet.com/static/share/2_blocks.sql.gz](https://samouraiwallet.com/static/share/2_blocks.sql.gz) to the "/db-scripts/" directory. Don't modify the name of the archive. +``` + + +#### Start the installation of Dojo #### + +``` +./dojo.sh install +``` + + + + +## bitcoind RPC API ans ZMQ notifications exposed to external apps ## + +By default, access to the RPC API of your bitcoind is restricted to Docker containers hosted on the "dojonet" network. + +The following steps allow to expose the RPC API and ZMQ notifications to applications running on your local machine but outside of Docker. + +``` +# +# If your Docker runs on macos or windows, +# retrieve the local IP address of the VM +# hosting your Docker containers +# + +# Stop your Dojo +./dojo.sh stop + +# Edit the bitcoin config file +nano ./conf/docker-bitcoind.conf + +# +# Set the value of BITCOIND_RPC_EXTERNAL to "on" +# +# If your Docker runs on macos or windows, +# set the value of BITCOIND_RPC_EXTERNAL_IP to the IP address of the VM +# +# Save and exit nano +# + +# Start your Dojo +./dojo.sh start +``` + +With this setting, external applications running on your local machine should be able to access the following ports: +* 9500: bitcoind zmqpubhashtx notifications +* 9501: bitcoind zmqpubrawtx notifications +* 9502: bitcoind zmqpubhashblock notifications +* 9503: bitcoind zmqpubrawblock notifications +* 28256: bitcoind RPC API + +Note: this option has no effect if your setup relies on a external full node (i.e. if BITCOIND_INSTALL is set to "off"). + + + + +## Static onion address for bitcoind hidden service ## + +By default, Dojo creates a new onion address for your bitcoind at each startup. + +The following steps allow to keep a static onion address (not recommended). + +``` +# Stop your Dojo +./dojo.sh stop + +# Edit the bitcoin config file +nano ./conf/docker-bitcoind.conf + +# +# Set the value of BITCOIND_EPHEMERAL_HS to "off" +# + +# Start your Dojo +./dojo.sh start +``` + +Note: this option has no effect if your setup relies on a external full node (i.e. if BITCOIND_INSTALL is set to "off"). diff --git a/doc/DOCKER_mac_setup.MD b/doc/DOCKER_mac_setup.MD new file mode 100644 index 0000000..2802aa0 --- /dev/null +++ b/doc/DOCKER_mac_setup.MD @@ -0,0 +1,54 @@ +# MacOS Installation + +This installation was tested on an iMac (mid 2011) with a 2.7GHz i5 processor with 8GB RAM and 1TB external Hard Drive. + +## Getting Started + +#### Create a new user: + +1. Launch System Preferences by clicking the **System Preferences** icon in the **Dock**, or selecting **System Preferences** from the Apple menu. +2. Click on __Users & Groups__ +3. If settings are locked, click on the __Lock__ at the bottom of window and enter your password. +4. Click on __+__ to add a new User +5. Under __New Account__ select __Administrator__ +6. Fill the remaining fields with your choice of User Name and Password + +#### Move the __New User Folder__ into the __External HD__ +Note: _This is an important step, otherwise, it's probable that when you run the container, it will be installed in your main OS Hard Drive and will run out of space as it validates the Bitcoin blockchain._ + +1. Open **Finder** and navigate to your startup drive's **/Users** folder. For most people, this is **/Macintosh HD/Users**. In the **Users** **folder**, you'll find your user's folder. +2. On your external Hard Drive, create a folder named **Users**. +3. Select your user folder and drag it to the external HD **/Users** folder you created. _Because you're using a different drive for the destination, the operating system will copy the data rather than move it. This ok for now but delete it later._ +4. Launch System Preferences again. +5. In the **Users & Groups** click the lock icon in the bottom left corner, then provide an administrator name and password. +6. From the list of user accounts, right-click on the account whose home folder you moved, and select **Advanced Options** from the pop-up menu. + _Do not make any changes to Advanced Options except for those noted here. Doing so can cause quite a few unforeseen problems that could lead to data loss or the need to reinstall the operating system._ +7. In the **Advanced Options** sheet, click **Choose**, located to the right of the **Home directory** field. +8. Navigate to the location you moved your home folder to, select the new home folder, and click **OK**. +9. Click **OK** to dismiss the **Advanced Options** sheet, and then close **System Preferences**. +10. __Restart your Mac__ + +#### Download and install Docker, Kitematic and TOR +1. Make sure your system fills the [requirements]([https://docs.docker.com/docker-for-mac/install/](https://docs.docker.com/docker-for-mac/install/)) (particularly MacOS Sierra 10.12 or higher. If not, upgrade before proceeding). +2. [Download Docker]([https://docs.docker.com/docker-for-mac/install/](https://docs.docker.com/docker-for-mac/install/)) and follow the installation steps. +3. _Optional_: Download [Kitematic]([https://kitematic.com/) and follow installation instructions. +(_This may be system specific but I've found that monitoring the logs with Kitematic was more stable than using the Terminal_). +4. Install [Tor Browser](https://www.torproject.org/projects/torbrowser.html.en) on the host machine. + +## Adjust Docker Settings +1. Click on the Docker icon (![whale menu](https://docs.docker.com/docker-for-mac/images/whale-x.png)) at the status bar and select __Preferences__. +2. Under Disk, click on __Reveal in Finder__ and double check that the disk image is saved under the external HD. +3. __Adjust Disk__ Image size to 400GB+ and click Apply. +4. Click __Advanced__ and increase the CPU count, Memory and Swap sizes. Adjusting these will speed up the blockchain validation process +(_At 4 CPUs, 8GB of RAM and a 4GiB Swap - the initial block download took 4.5 days at the time of writing_). + +## Install the DOJO +Follow the instructions [here](https://github.com/Samourai-Wallet/samourai-dojo/blob/develop/doc/DOCKER_setup.md) starting at the step: +__"Download the most recent release of Dojo from Github"__ +_Note: For tracking progress, open Kitematic and follow the bitcoind logs. You'll be able to see the Blockchain verification process under the _progress_ log variable (1.00 = fully validated). This process takes a long time. Just let it do its thing. In my system it took 3 days._ + +__Some possible optimization tips:__ +. If you notice that progress has stopped. Click the whale icon and select Restart. Check Kitematic logs of bitcoind to confirm that progress has resumed. +. This may optimize speed: open __Activity Monitor__, check the PID (Process ID) of your docker process. Open Terminal and type: + +`sudo renice-20 -p [enter your PID]` diff --git a/doc/DOCKER_setup.md b/doc/DOCKER_setup.md index 3103c88..f92fd55 100644 --- a/doc/DOCKER_setup.md +++ b/doc/DOCKER_setup.md @@ -2,11 +2,25 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed of: * a bitcoin full node accessible as an ephemeral Tor hidden service, -* the backend database, -* the backend modules with an API accessible as a static Tor hidden service, +* a backend database, +* a backend modules with an API accessible as a static Tor hidden service, * a maintenance tool accessible through a Tor web browser. +## Table of Content ## +- [Architecture](#architecture) +- [Requirements](#requirements) +- [First-time install procedure](#install) +- [Upgrade procedure](#upgrade) +- [Configuration files](#config_files) +- [Dojo shell script](#shell_script) +- [Dojo maintenance tool](#maintenance_tool) +- [Pairing your wallet to your Dojo](#pairing) +- [Network connections](#network) + + + + ## Architecture ## @@ -21,7 +35,7 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed ------------ | - Host machine | (Tor - port 80) + Host machine | (Tor hidden services) ______________________________ | _____________________________ | | | | ------------------- | @@ -42,6 +56,7 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed |______________________________________________________________| + ## Requirements ## @@ -54,41 +69,52 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed * Tor Browser installed on the host machine (or on another machine if your host is a headless server) -## Setup ## + -* Install [Docker and Docker Compose](https://docs.docker.com/compose/install/) on the host machine and check that your installation is working. +## First-time Setup ## -* Install [Tor Browser](https://www.torproject.org/projects/torbrowser.html.en) on the host machine. +For MacOS, see this detailed [installation guide](./DOCKER_mac_setup.MD). -* Download the most recent version of Dojo from [Github](https://github.com/Samourai-Wallet/samourai-dojo/archive/master.zip) +This procedure allows to install a new Dojo from scratch. -* Uncompress the archive on the host machine in a temporary directory of your choice (named /tmp_dir in this doc) +* Install [Docker and Docker Compose](https://docs.docker.com/compose/install/) on the host machine and check that your installation is working. -* Create a directory for Dojo (named /dojo_dir in this doc) +* Install [Tor Browser](https://www.torproject.org/projects/torbrowser.html.en) on the host machine. -* Copy the content of the "/tmp_dir/samourai-dojo-master" directory into the "/dojo_dir" directory +* Download the most recent release of Dojo from [Github](https://github.com/Samourai-Wallet/samourai-dojo/archive/master.zip) -* Customize the configuration of your Dojo +* Uncompress the archive on the host machine in a temporary directory of your choice (named `` in this doc) - * Go to the "/dojo_dir/docker/my_dojo/conf" directory +* Create a directory for Dojo (named `` in this doc) - * Edit docker-bitcoind.conf and provide a new value for the following parameters: - * BITCOIND_RPC_USER = login protecting the access to the RPC API of your full node, - * BITCOIND_RPC_PASSWORD = password protecting the access to the RPC API of your full node. - * If your machine has a lot of RAM, it's recommended that you increase the value of BITCOIND_DB_CACHE for a faster Initial Block Download. +* Copy the content of the `/samourai-dojo-master` directory into the `` directory - * Edit docker-mysql.conf and provide a new value for the following parameters: - * MYSQL_ROOT_PASSWORD = password protecting the root account of MySQL, - * MYSQL_USER = login of the account used to access the database of your Dojo, - * MYSQL_PASSWORD = password of the account used to access the database of your Dojo. +* Customize the configuration of your Dojo - * Edit docker-node.conf and provide a new value for the following parameters: - * NODE_API_KEY = API key which will be required from your Samourai Wallet / Sentinel for its interactions with the API of your Dojo, - * NODE_ADMIN_KEY = API key which will be required from the maintenance tool for accessing a set of advanced features provided by the API of your Dojo, - * NODE_JWT_SECRET = secret used by your Dojo for the initialization of a cryptographic key signing Json Web Tokens. + * Go to the `/docker/my-dojo/conf` directory + + * Edit docker-bitcoind.conf.tpl and provide a new value for the following parameters: + * `BITCOIND_RPC_USER` = login protecting the access to the RPC API of your full node, + * `BITCOIND_RPC_PASSWORD` = password protecting the access to the RPC API of your full node. + * If your machine has a lot of RAM, it's recommended that you increase the value of `BITCOIND_DB_CACHE` for a faster Initial Block Download. + * This file also provides a few additional settings for advanced setups: + * static onion address for your full node, + * bitcoind RPC API exposed to external apps, + * use of an external full node. + See this [doc](./DOCKER_advanced_setups.md) for more details. + + * Edit docker-mysql.conf.tpl and provide a new value for the following parameters: + * `MYSQL_ROOT_PASSWORD` = password protecting the root account of MySQL, + * `MYSQL_USER` = login of the account used to access the database of your Dojo, + * `MYSQL_PASSWORD` = password of the account used to access the database of your Dojo. + + * Edit docker-node.conf.tpl and provide a new value for the following parameters: + * `NODE_API_KEY` = API key which will be required from your Samourai Wallet / Sentinel for its interactions with the API of your Dojo, + * `NODE_ADMIN_KEY` = API key which will be required from the maintenance tool for accessing a set of advanced features provided by the API of your Dojo, + * `NODE_JWT_SECRET` = secret used by your Dojo for the initialization of a cryptographic key signing Json Web Tokens. These parameters will protect the access to your Dojo. Be sure to provide alphanumeric values with enough entropy. -* Open the docker quickstart terminal or a terminal console and go to the "/dojo_dir/docker/my_dojo" directory. This directory contains a script named dojo.sh which will be your entrypoint for all operations related to the management of your Dojo. +* Open the docker quickstart terminal or a terminal console and go to the `/docker/my-dojo` directory. This directory contains a script named dojo.sh which will be your entrypoint for all operations related to the management of your Dojo. * Launch the installation of your Dojo with @@ -118,6 +144,59 @@ Exit the logs with CTRL+C when the syncing of the database has completed. * Restrict the access to your host machine as much as possible by configuring its firewall. + + +## Upgrade ## + +This procedure allows to upgrade your Dojo with a new version. + +* Stop your Dojo with + +``` +./dojo.sh stop +``` + +* Download the most recent release of Dojo from [Github](https://github.com/Samourai-Wallet/samourai-dojo/releases) + +* Uncompress the archive on the host machine in a temporary directory of your choice (named `` in this doc) + +* Copy the content of the `/samourai-dojo-1.x.y` directory into the `` directory (overwrite). + +* Launch the upgrade of your Dojo with + +``` +./dojo.sh upgrade +``` + +Docker and Docker Compose are going to build new images and containers for your Dojo. After completion, the updated version of your Dojo will be launched automatically. + +Note: The upgrade process will override all manual modifications of the files stored under the `` directory with an exception for the three configuration files stored in the `/docker/my-dojo/conf` directory. + + + + +## Configuration files ## + +Each new release of Dojo is packaged with 3 template files stored in the `/docker/my-dojo/conf` directory: +- docker-bitcoin.conf.tpl +- docker-mysql.conf.tpl +- docker-node.conf.tpl + +These templates files define default values for configuration options of your Dojo. + +During the first-time installation (dojo.sh install) these templates are used to initialize the configuration files (files with .conf extension) that will be used by your Dojo. + +During an upgrade (dojo.sh upgrade), the content of the templates files is merged with the content of the configuration files, preserving the values that you may have modified in the configuration files. A backup of the configuration files is saved in the same directory (files with .save extension). + +Most options provided in the configuration files can be later modified. New values will become active after a call to + +``` +./dojo.sh restart +``` + + + + ## Dojo shell script ## dojo.sh is a multifeature tool allowing to interact with your Dojo. @@ -131,6 +210,8 @@ Available commands: bitcoin-cli Launch a bitcoin-cli console for interacting with bitcoind RPC API. + clean Free disk space by deleting docker dangling images and images of previous versions. + install Install your Dojo. logs [module] [options] Display the logs of your Dojo. Use CTRL+C to stop the logs. @@ -160,28 +241,35 @@ Available commands: uninstall Delete your Dojo. Be careful! This command will also remove all data. + upgrade Upgrade your Dojo. + + version Display the version of dojo. ``` + + ## Dojo maintenance tool ## -A maintenance tool is accessible through your Tor browser at the url: /admin +A maintenance tool is accessible through your Tor browser at the url: /admin The maintenance tool requires that you allow javascript for the site. -Sign in with the value entered for NODE_ADMIN_KEY. +Sign in with the value entered for `NODE_ADMIN_KEY`. + -## Pairing ## +## Pairing your wallet to your Dojo ## Once the database has finished syncing, you can pair your Samourai Wallet with your Dojo in 2 steps: -* Open the maintenance tool in a Tor browser and sign in with your admin key. +* Open the maintenance tool in a Tor browser (Tor v3 onion address) and sign in with your admin key. * Get your smartphone and launch the Samourai Wallet app. Scan the QRCode displayed in the "Pairing" tab of the maintenance tool. + ## Network connections ## @@ -191,6 +279,6 @@ If OXT is selected as the default source for imports, OXT clearnet API is access The maintenance tool is accessed as a Tor hidden service (static onion address). -The Bitcoin node only allows incoming connections from Tor (dynamic onion address). +The Bitcoin node only allows incoming connections from Tor (ephemeral onion address). -The Bitcoin node attempts outgoing connections to both Tor and clearrnet nodes (through the Tor local proxy). +The Bitcoin node attempts outgoing connections to both Tor and clearnet nodes (through the Tor local proxy). diff --git a/doc/GET_multiaddr.md b/doc/GET_multiaddr.md index 92ebb2f..d2ae1ee 100644 --- a/doc/GET_multiaddr.md +++ b/doc/GET_multiaddr.md @@ -1,10 +1,31 @@ # Get Multiaddr -Request details about a collection of HD accounts and/or loose addresses and/or public keys. If accounts do not exist, they will be created with a relayed call to the [POST /xpub](./POST_xpub.md) mechanics if new or will be imported from external data sources. Instruct the server that entities are new with `?new=xpub1|addr2|addr3` in the query parameters, and the server will skip importing for those entities. SegWit support via BIP49 is activated for new xpubs with `?bip49=xpub3|xpub4`. SegWit support via BIP84 is activated for new xpubs with `?bip84=xpub3|xpub4`. Pass xpubs to `?bip49` or `?bip84` only for newly-created accounts. Support of BIP47 (with addresses derived in 3 formats (P2PKH, P2WPKH/P2SH, P2WPKH Bech32)) is activated for new pubkeys with `?pubkey=pubkey1|pubkey2`. +Request details about a collection of HD accounts and/or loose addresses and/or pubkeys (derived in 3 formats P2PKH, P2WPKH/P2SH, P2WPKH Bech32). + + +## Behavior of the active parameter + +If accounts passed to `?active` do not exist, they will be created with a relayed call to the [POST /xpub](./POST_xpub.md) mechanics if new or will be imported from external data sources. + +If loose addresses passed to `?active` do not exist, they will be imported from external data sources. + +If addresses derived from pubkeys passed to `?active` do not exist, they will be imported from external data sources. + + +## Declaration of new entities + +Instruct the server that [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) entities are new with `?new=xpub1|addr2|addr3` in the query parameters, and the server will skip importing for those entities. + +SegWit support via [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) is activated for new ypubs and new P2WPKH/P2SH loose addresses with `?bip49=xpub3|xpub4`. + +SegWit support via [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) is activated for new zpubs and new P2WPKH Bech32 loose addresses with `?bip84=xpub3|xpub4`. + +Support of [BIP47](https://github.com/bitcoin/bips/blob/master/bip-0047.mediawiki) with addresses derived in 3 formats (P2PKH, P2WPKH/P2SH, P2WPKH Bech32) is activated for new pubkeys with `?pubkey=pubkey1|pubkey2`. + Note that loose addresses that are also part of one of the HD accounts requested will be ignored. Their balances and transactions are listed as part of the HD account result. -The `POST` version of multiaddr is identical, except the `active` and `new` parameters are in the POST body. +The `POST` version of multiaddr is identical, except the parameters are in the POST body. ``` @@ -13,10 +34,10 @@ GET /multiaddr?active=...[&new=...][&bip49=...][&bip84=...][&pubkey=...] ## Parameters * **active** - `string` - A pipe-separated list of extended public keys and/or loose addresses and/or pubkeys (`xpub1|address1|address2|pubkey1|...`) -* **new** - `string` - A pipe-separated list of extended public keys and/or loose addresses that need no import from external services -* **bip49** - `string` - A pipe-separated list of new extended public keys to be derived via [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) -* **bip84** - `string` - A pipe-separated list of new extended public keys to be derived via [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) -* **pubkey** - `string` - A pipe-separated list of public keys to be derived as P2PKH, P2WPKH/P2SH, P2WPKH Bech32 addresses. +* **new** - `string` - A pipe-separated list of **new** extended public keys to be derived via [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) and/or new P2PKH loose addresses +* **bip49** - `string` - A pipe-separated list of **new** extended public keys to be derived via [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) and/or new P2WPKH/P2SH loose addresses +* **bip84** - `string` - A pipe-separated list of **new** extended public keys to be derived via [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) and/or new P2WPKH Bech32 loose addresses +* **pubkey** - `string` - A pipe-separated list of **new** public keys to be derived as P2PKH, P2WPKH/P2SH, P2WPKH Bech32 addresses * **at** - `string` (optional) - Access Token (json web token). Required if authentication is activated. ### Examples diff --git a/doc/GET_unspent.md b/doc/GET_unspent.md index cb3b6a1..427960f 100644 --- a/doc/GET_unspent.md +++ b/doc/GET_unspent.md @@ -1,6 +1,27 @@ # Get Unspent -Request a list of unspent transaction outputs from a collection of HD accounts and/or loose addresses and or public keys. If accounts do not exist, they will be created with a relayed call to the [POST /xpub](./POST_xpub.md) mechanics if new or will be imported from external data sources. Instruct the server that entities are new with `?new=xpub1|addr2|addr3` in the query parameters. SegWit support via BIP49 is activated for new xpubs with `?bip49=xpub3|xpub4`. SegWit support via BIP84 is activated for new xpubs with `?bip84=xpub3|xpub4`. Pass xpubs to `?bip49` or `?bip84` only for newly-created accounts. Support of BIP47 (with addresses derived in 3 formats (P2PKH, P2WPKH/P2SH, P2WPKH Bech32)) is activated for new pubkeys with `?pubkey=pubkey1|pubkey2`. +Request a list of unspent transaction outputs from a collection of HD accounts and/or loose addresses and/or pubkeys (derived in 3 formats P2PKH, P2WPKH/P2SH, P2WPKH Bech32). + + +## Behavior of the active parameter + +If accounts passed to `?active` do not exist, they will be created with a relayed call to the [POST /xpub](./POST_xpub.md) mechanics if new or will be imported from external data sources. + +If loose addresses passed to `?active` do not exist, they will be imported from external data sources. + +If addresses derived from pubkeys passed to `?active` do not exist, they will be imported from external data sources. + + +## Declaration of new entities + +Instruct the server that [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) entities are new with `?new=xpub1|addr2|addr3` in the query parameters, and the server will skip importing for those entities. + +SegWit support via [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) is activated for new ypubs and new P2WPKH/P2SH loose addresses with `?bip49=xpub3|xpub4`. + +SegWit support via [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) is activated for new zpubs and new P2WPKH Bech32 loose addresses with `?bip84=xpub3|xpub4`. + +Support of [BIP47](https://github.com/bitcoin/bips/blob/master/bip-0047.mediawiki) with addresses derived in 3 formats (P2PKH, P2WPKH/P2SH, P2WPKH Bech32) is activated for new pubkeys with `?pubkey=pubkey1|pubkey2`. + The `POST` version of unspent is identical, except the parameters are in the POST body. @@ -10,11 +31,11 @@ GET /unspent?active=...&new=...&bip49=...&bip84=...&pubkey=... ``` ## Parameters -* **active** - `string` - A pipe-separated list of extended public keys and/or loose addresses (`xpub1|address1|address2|...`) -* **new** - `string` - A pipe-separated list of extended public keys and/or loose addresses that need no import from external services -* **bip49** - `string` - A pipe-separated list of new extended public keys to be derived via [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) -* **bip84** - `string` - A pipe-separated list of new extended public keys to be derived via [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) -* **pubkey** - `string` - A pipe-separated list of public keys to be derived as P2PKH, P2WPKH/P2SH, P2WPKH Bech32 addresses. +* **active** - `string` - A pipe-separated list of extended public keys and/or loose addresses and/or pubkeys (`xpub1|address1|address2|pubkey1|...`) +* **new** - `string` - A pipe-separated list of **new** extended public keys to be derived via [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) and/or new P2PKH loose addresses +* **bip49** - `string` - A pipe-separated list of **new** extended public keys to be derived via [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) and/or new P2WPKH/P2SH loose addresses +* **bip84** - `string` - A pipe-separated list of **new** extended public keys to be derived via [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) and/or new P2WPKH Bech32 loose addresses +* **pubkey** - `string` - A pipe-separated list of **new** public keys to be derived as P2PKH, P2WPKH/P2SH, P2WPKH Bech32 addresses * **at** - `string` (optional) - Access Token (json web token). Required if authentication is activated. ### Examples diff --git a/docker/my-dojo/.env b/docker/my-dojo/.env index a51bc5b..155846b 100644 --- a/docker/my-dojo/.env +++ b/docker/my-dojo/.env @@ -9,7 +9,13 @@ ######################################### COMPOSE_CONVERT_WINDOWS_PATHS=1 -DOJO_VERSION_TAG=1.0.0 + +DOJO_VERSION_TAG=1.1.0 +DOJO_DB_VERSION_TAG=1.1.0 +DOJO_BITCOIND_VERSION_TAG=1.1.0 +DOJO_NODEJS_VERSION_TAG=1.1.0 +DOJO_NGINX_VERSION_TAG=1.1.0 +DOJO_TOR_VERSION_TAG=1.1.0 ######################################### diff --git a/docker/my-dojo/bitcoin/Dockerfile b/docker/my-dojo/bitcoin/Dockerfile index 706238d..5f1c454 100644 --- a/docker/my-dojo/bitcoin/Dockerfile +++ b/docker/my-dojo/bitcoin/Dockerfile @@ -27,18 +27,17 @@ RUN set -ex && \ tar -xzvf bitcoin.tar.gz -C /usr/local --strip-components=1 --exclude=*-qt && \ rm -rf /tmp/* -# Create group & user bitcoin +# Create groups bitcoin & tor +# Create user bitcoin and add it to groups RUN addgroup --system -gid 1108 bitcoin && \ - adduser --system --ingroup bitcoin -uid 1105 bitcoin + addgroup --system -gid 1107 tor && \ + adduser --system --ingroup bitcoin -uid 1105 bitcoin && \ + usermod -a -G tor bitcoin # Create data directory RUN mkdir "$BITCOIN_HOME/.bitcoin" && \ chown -h bitcoin:bitcoin "$BITCOIN_HOME/.bitcoin" -# Copy bitcoin config file -COPY ./bitcoin.conf "$BITCOIN_HOME/.bitcoin/bitcoin.conf" -RUN chown bitcoin:bitcoin "$BITCOIN_HOME/.bitcoin/bitcoin.conf" - # Copy restart script COPY ./restart.sh /restart.sh RUN chown bitcoin:bitcoin /restart.sh && \ diff --git a/docker/my-dojo/bitcoin/bitcoin.conf b/docker/my-dojo/bitcoin/bitcoin.conf deleted file mode 100644 index fe7167d..0000000 --- a/docker/my-dojo/bitcoin/bitcoin.conf +++ /dev/null @@ -1,22 +0,0 @@ -# Bitcoin Configuration -server=1 -listen=1 -bind=127.0.0.1 - -# Tor proxy through dojonet -proxy=172.28.1.4:9050 - -# Non-default RPC Port -rpcport=28256 -rpcallowip=::/0 -rpcbind=bitcoind - -# Store transaction information for fully-spent txns -txindex=1 - -# No wallet -disablewallet=1 - -# ZeroMQ Notification Settings -zmqpubhashblock=tcp://0.0.0.0:9502 -zmqpubrawtx=tcp://0.0.0.0:9501 diff --git a/docker/my-dojo/bitcoin/restart.sh b/docker/my-dojo/bitcoin/restart.sh index 8a53527..2351c70 100644 --- a/docker/my-dojo/bitcoin/restart.sh +++ b/docker/my-dojo/bitcoin/restart.sh @@ -2,13 +2,37 @@ set -e echo "## Start bitcoind #############################" -bitcoind -datadir=/home/bitcoin/.bitcoin \ - -dbcache=$BITCOIND_DB_CACHE \ - -dnsseed=$BITCOIND_DNSSEED \ - -dns=$BITCOIND_DNS \ - -rpcuser=$BITCOIND_RPC_USER \ - -rpcpassword=$BITCOIND_RPC_PASSWORD \ - -maxconnections=$BITCOIND_MAX_CONNECTIONS \ - -maxmempool=$BITCOIND_MAX_MEMPOOL \ - -mempoolexpiry=$BITCOIND_MEMPOOL_EXPIRY \ + +bitcoind_options=( + -bind=172.28.1.5 + -datadir=/home/bitcoin/.bitcoin + -dbcache=$BITCOIND_DB_CACHE + -disablewallet=1 + -dns=$BITCOIND_DNS + -dnsseed=$BITCOIND_DNSSEED + -externalip=$(cat /var/lib/tor/hsv2bitcoind/hostname) + -listen=1 + -maxconnections=$BITCOIND_MAX_CONNECTIONS + -maxmempool=$BITCOIND_MAX_MEMPOOL + -mempoolexpiry=$BITCOIND_MEMPOOL_EXPIRY -minrelaytxfee=$BITCOIND_MIN_RELAY_TX_FEE + -port=8333 + -proxy=172.28.1.4:9050 + -rpcallowip=::/0 + -rpcbind=172.28.1.5 + -rpcpassword=$BITCOIND_RPC_PASSWORD + -rpcport=28256 + -rpcthreads=$BITCOIND_RPC_THREADS + -rpcuser=$BITCOIND_RPC_USER + -server=1 + -txindex=1 + -zmqpubhashblock=tcp://0.0.0.0:9502 + -zmqpubrawtx=tcp://0.0.0.0:9501 +) + +if [ "$BITCOIND_RPC_EXTERNAL" == "on" ]; then + bitcoind_options+=(-zmqpubhashtx=tcp://0.0.0.0:9500) + bitcoind_options+=(-zmqpubrawblock=tcp://0.0.0.0:9503) +fi + +bitcoind "${bitcoind_options[@]}" diff --git a/docker/my-dojo/conf/docker-bitcoind.conf b/docker/my-dojo/conf/docker-bitcoind.conf deleted file mode 100644 index 9957409..0000000 --- a/docker/my-dojo/conf/docker-bitcoind.conf +++ /dev/null @@ -1,32 +0,0 @@ -######################################### -# CONFIGURATION OF BITCOIND CONTAINER -######################################### - -# User account used for rpc access to bitcoind -# Type: alphanumeric -BITCOIND_RPC_USER=dojorpc - -# Password of user account used for rpc access to bitcoind -# Type: alphanumeric -BITCOIND_RPC_PASSWORD=dojorpcpassword - -# Max number of connections to network peers -# Type: integer -BITCOIND_MAX_CONNECTIONS=16 - -# Mempool maximum size in MB -# Type: integer -BITCOIND_MAX_MEMPOOL=1024 - -# Db cache size in MB -# Type: integer -BITCOIND_DB_CACHE=1024 - -# Mempool expiry in hours -# Defines how long transactions stay in your local mempool before expiring -# Type: integer -BITCOIND_MEMPOOL_EXPIRY=72 - -# Min relay tx fee in BTC -# Type: numeric -BITCOIND_MIN_RELAY_TX_FEE=0.00001 \ No newline at end of file diff --git a/docker/my-dojo/conf/docker-bitcoind.conf.tpl b/docker/my-dojo/conf/docker-bitcoind.conf.tpl new file mode 100644 index 0000000..a79e4e8 --- /dev/null +++ b/docker/my-dojo/conf/docker-bitcoind.conf.tpl @@ -0,0 +1,103 @@ +######################################### +# CONFIGURATION OF BITCOIND CONTAINER +######################################### + +# User account used for rpc access to bitcoind +# Type: alphanumeric +BITCOIND_RPC_USER=dojorpc + +# Password of user account used for rpc access to bitcoind +# Type: alphanumeric +BITCOIND_RPC_PASSWORD=dojorpcpassword + +# Max number of connections to network peers +# Type: integer +BITCOIND_MAX_CONNECTIONS=16 + +# Mempool maximum size in MB +# Type: integer +BITCOIND_MAX_MEMPOOL=1024 + +# Db cache size in MB +# Type: integer +BITCOIND_DB_CACHE=1024 + +# Number of threads to service RPC calls +# Type: integer +BITCOIND_RPC_THREADS=6 + +# Mempool expiry in hours +# Defines how long transactions stay in your local mempool before expiring +# Type: integer +BITCOIND_MEMPOOL_EXPIRY=72 + +# Min relay tx fee in BTC +# Type: numeric +BITCOIND_MIN_RELAY_TX_FEE=0.00001 + + +# +# EXPERT SETTINGS +# + + +# +# EPHEMERAL ONION ADDRESS FOR BITCOIND +# THIS PARAMETER HAS NO EFFECT IF BITCOIND_INSTALL IS SET TO OFF +# + +# Generate a new onion address for bitcoind when Dojo is launched +# Activation of this option is recommended for improved privacy. +# Values: on | off +BITCOIND_EPHEMERAL_HS=on + + +# +# EXPOSE BITCOIND RPC API AND ZMQ NOTIFICATIONS TO EXTERNAL APPS +# THESE PARAMETERS HAVE NO EFFECT IF BITCOIND_INSTALL IS SET TO OFF +# + +# Expose the RPC API to external apps +# Warning: Do not expose your RPC API to internet! +# See BITCOIND_RPC_EXTERNAL_IP +# Value: on | off +BITCOIND_RPC_EXTERNAL=off + +# IP address used to expose the RPC API to external apps +# This parameter is inactive if BITCOIND_RPC_EXTERNAL isn't set to 'on' +# Warning: Do not expose your RPC API to internet! +# Recommended value: +# linux: 127.0.0.1 +# macos or windows: IP address of the VM running the docker host +# Type: string +BITCOIND_RPC_EXTERNAL_IP=127.0.0.1 + + +# +# INSTALL AND RUN BITCOIND INSIDE DOCKER +# + +# Install and run bitcoind inside Docker +# Set this option to 'off' for using a bitcoind hosted outside of Docker (not recommended) +# Value: on | off +BITCOIND_INSTALL=on + +# IP address of bitcoind used by Dojo +# Set value to 172.28.1.5 if BITCOIND_INSTALL is set to 'on' +# Type: string +BITCOIND_IP=172.28.1.5 + +# Port of the RPC API +# Set value to 28256 if BITCOIND_INSTALL is set to 'on' +# Type: integer +BITCOIND_RPC_PORT=28256 + +# Port exposing ZMQ notifications for raw transactions +# Set value to 9501 if BITCOIND_INSTALL is set to 'on' +# Type: integer +BITCOIND_ZMQ_RAWTXS=9501 + +# Port exposing ZMQ notifications for block hashes +# Set value to 9502 if BITCOIND_INSTALL is set to 'on' +# Type: integer +BITCOIND_ZMQ_BLK_HASH=9502 \ No newline at end of file diff --git a/docker/my-dojo/conf/docker-mysql.conf b/docker/my-dojo/conf/docker-mysql.conf.tpl similarity index 100% rename from docker/my-dojo/conf/docker-mysql.conf rename to docker/my-dojo/conf/docker-mysql.conf.tpl diff --git a/docker/my-dojo/conf/docker-node.conf b/docker/my-dojo/conf/docker-node.conf.tpl similarity index 100% rename from docker/my-dojo/conf/docker-node.conf rename to docker/my-dojo/conf/docker-node.conf.tpl diff --git a/docker/my-dojo/docker-compose.yaml b/docker/my-dojo/docker-compose.yaml index 980e978..b679107 100644 --- a/docker/my-dojo/docker-compose.yaml +++ b/docker/my-dojo/docker-compose.yaml @@ -2,7 +2,7 @@ version: "3.2" services: db: - image: "samouraiwallet/dojo-db:1.0.0" + image: "samouraiwallet/dojo-db:${DOJO_DB_VERSION_TAG}" container_name: db build: context: ./../.. @@ -19,31 +19,8 @@ services: dojonet: ipv4_address: 172.28.1.1 - bitcoind: - image: "samouraiwallet/dojo-bitcoind:1.0.0" - container_name: bitcoind - build: - context: ./bitcoin - env_file: - - ./.env - - ./conf/docker-bitcoind.conf - restart: on-failure - command: "/wait-for-it.sh tor:9050 --timeout=360 --strict -- /restart.sh" - expose: - - "28256" - - "9501" - - "9502" - volumes: - - data-bitcoind:/home/bitcoin/.bitcoin - depends_on: - - db - - tor - networks: - dojonet: - ipv4_address: 172.28.1.5 - node: - image: "samouraiwallet/dojo-nodejs:1.0.0" + image: "samouraiwallet/dojo-nodejs:${DOJO_NODEJS_VERSION_TAG}" container_name: nodejs build: context: ./../.. @@ -61,14 +38,13 @@ services: volumes: - data-nodejs:/data depends_on: - - bitcoind - db networks: dojonet: ipv4_address: 172.28.1.2 nginx: - image: "samouraiwallet/dojo-nginx:1.0.0" + image: "samouraiwallet/dojo-nginx:${DOJO_NGINX_VERSION_TAG}" container_name: nginx build: context: ./nginx @@ -89,16 +65,14 @@ services: ipv4_address: 172.28.1.3 tor: - image: "samouraiwallet/dojo-tor:1.0.0" + image: "samouraiwallet/dojo-tor:${DOJO_TOR_VERSION_TAG}" container_name: tor build: context: ./tor env_file: - ./.env restart: on-failure - command: tor - ports: - - "80:80" + command: /restart.sh volumes: - data-tor:/var/lib/tor networks: @@ -123,8 +97,6 @@ networks: volumes: data-mysql: - data-bitcoind: - data-bitcoind-tor: data-nodejs: data-nginx: data-tor: diff --git a/docker/my-dojo/dojo.sh b/docker/my-dojo/dojo.sh index 453c345..0cff22e 100755 --- a/docker/my-dojo/dojo.sh +++ b/docker/my-dojo/dojo.sh @@ -1,72 +1,179 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -source "$DIR/conf/docker-bitcoind.conf" +# Source a file +source_file() { + if [ -f $1 ]; then + source $1 + fi +} + +source_file "$DIR/conf/docker-bitcoind.conf" +source_file "$DIR/.env" + + +# Select YAML files +select_yaml_files() { + source_file "$DIR/conf/docker-bitcoind.conf" + + yamlFiles="-f $DIR/docker-compose.yaml" + + if [ "$BITCOIND_INSTALL" == "on" ]; then + yamlFiles="$yamlFiles -f $DIR/overrides/bitcoind.install.yaml" + + if [ "$BITCOIND_RPC_EXTERNAL" == "on" ]; then + yamlFiles="$yamlFiles -f $DIR/overrides/bitcoind.rpc.expose.yaml" + export BITCOIND_RPC_EXTERNAL_IP + fi + fi + + # Return yamlFiles + echo "$yamlFiles" +} + +# Docker up +docker_up() { + yamlFiles=$(select_yaml_files) + eval "docker-compose $yamlFiles up $1 -d" +} # Start start() { - docker-compose up --remove-orphans -d + docker_up --remove-orphans } # Stop stop() { - docker exec -it bitcoind bitcoin-cli \ - -rpcconnect=bitcoind \ - --rpcport=28256 \ - --rpcuser="$BITCOIND_RPC_USER" \ - --rpcpassword="$BITCOIND_RPC_PASSWORD" \ - stop + if [ "$BITCOIND_INSTALL" == "on" ]; then + if [ "$BITCOIND_EPHEMERAL_HS" = "on" ]; then + docker exec -it tor rm -rf /var/lib/tor/hsv2bitcoind + fi - echo "Preparing shutdown of dojo. Please wait." - sleep 15s + docker exec -it bitcoind bitcoin-cli \ + -rpcconnect=bitcoind \ + --rpcport=28256 \ + --rpcuser="$BITCOIND_RPC_USER" \ + --rpcpassword="$BITCOIND_RPC_PASSWORD" \ + stop - docker-compose down + echo "Preparing shutdown of dojo. Please wait." + + bitcoindDown=$(timeout 3m docker wait bitcoind) + if [ $bitcoindDown -eq 0 ]; then + echo "Bitcoin server stopped." + else + echo "Force shutdown of Bitcoin server." + fi + fi + + yamlFiles=$(select_yaml_files) + eval "docker-compose $yamlFiles down" } # Restart dojo restart() { - docker exec -it bitcoind bitcoin-cli \ - -rpcconnect=bitcoind \ - --rpcport=28256 \ - --rpcuser="$BITCOIND_RPC_USER" \ - --rpcpassword="$BITCOIND_RPC_PASSWORD" \ - stop - - echo "Preparing shutdown of dojo. Please wait." - sleep 15s - - docker-compose down - docker-compose up -d + stop + docker_up } # Install install() { - docker-compose up -d --remove-orphans - docker-compose logs --tail=0 --follow + source "$DIR/install/install-scripts.sh" + + launchInstall=1 + + if [ -z "$1" ]; then + get_confirmation + launchInstall=$? + else + launchInstall=0 + fi + + if [ $launchInstall -eq 0 ]; then + init_config_files + docker_up --remove-orphans + logs + fi } # Delete everything uninstall() { docker-compose rm - docker-compose down - docker image rm samouraiwallet/dojo-db:1.0.0 - docker image rm samouraiwallet/dojo-bitcoind:1.0.0 - docker image rm samouraiwallet/dojo-nodejs:1.0.0 - docker image rm samouraiwallet/dojo-nginx:1.0.0 - docker image rm samouraiwallet/dojo-tor:1.0.0 + yamlFiles=$(select_yaml_files) + eval "docker-compose $yamlFiles down" + + docker image rm samouraiwallet/dojo-db:"$DOJO_DB_VERSION_TAG" + docker image rm samouraiwallet/dojo-bitcoind:"$DOJO_BITCOIND_VERSION_TAG" + docker image rm samouraiwallet/dojo-nodejs:"$DOJO_NODEJS_VERSION_TAG" + docker image rm samouraiwallet/dojo-nginx:"$DOJO_NGINX_VERSION_TAG" + docker image rm samouraiwallet/dojo-tor:"$DOJO_TOR_VERSION_TAG" docker volume prune } +# Clean-up (remove old docker images) +del_images_for() { + # $1: image name + # $2: most recent version of the image (do not delete this one) + docker image ls | grep "$1" | sed "s/ \+/,/g" | cut -d"," -f2 | while read -r version ; do + if [ "$2" != "$version" ]; then + docker image rm "$1:$version" + fi + done +} + +clean() { + docker image prune + del_images_for samouraiwallet/dojo-db "$DOJO_DB_VERSION_TAG" + del_images_for samouraiwallet/dojo-bitcoind "$DOJO_BITCOIND_VERSION_TAG" + del_images_for samouraiwallet/dojo-nodejs "$DOJO_NODEJS_VERSION_TAG" + del_images_for samouraiwallet/dojo-nginx "$DOJO_NGINX_VERSION_TAG" + del_images_for samouraiwallet/dojo-tor "$DOJO_TOR_VERSION_TAG" +} + +# Upgrade +upgrade() { + source "$DIR/install/upgrade-scripts.sh" + + launchUpgrade=1 + + if [ -z "$1" ]; then + get_confirmation + launchUpgrade=$? + else + launchUpgrade=0 + fi + + if [ $launchUpgrade -eq 0 ]; then + yamlFiles=$(select_yaml_files) + update_config_files + cleanup + eval "docker-compose $yamlFiles build --no-cache" + docker_up --remove-orphans + update_dojo_db + logs + fi +} + # Display the onion address onion() { V2_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv2dojo/hostname ) V3_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv3dojo/hostname ) + + echo "API hidden service address (v3) = $V3_ADDR" + echo "API hidden service address (v2) = $V2_ADDR" - echo "API Hidden Service address (v3) = $V3_ADDR" - echo "API Hidden Service address (v2) = $V2_ADDR" + if [ "$BITCOIND_INSTALL" == "on" ]; then + V2_ADDR_BTCD=$( docker exec -it tor cat /var/lib/tor/hsv2bitcoind/hostname ) + echo "bitcoind hidden service address (v2) = $V2_ADDR_BTCD" + fi +} + +# Display the version of this dojo +version() { + echo "Dojo v$DOJO_VERSION_TAG" } # Display logs @@ -79,12 +186,18 @@ logs_node() { } logs() { + source_file "$DIR/conf/docker-bitcoind.conf" + case $1 in db ) docker-compose logs --tail=50 --follow db ;; bitcoind ) - docker exec -ti bitcoind tail -f /home/bitcoin/.bitcoin/debug.log + if [ "$BITCOIND_INSTALL" == "on" ]; then + docker exec -ti bitcoind tail -f /home/bitcoin/.bitcoin/debug.log + else + echo -e "Command not supported for your setup.\nCause: Your Dojo is using an external bitcoind" + fi ;; tor ) docker-compose logs --tail=50 --follow tor @@ -93,7 +206,12 @@ logs() { logs_node $1 $2 $3 ;; * ) - docker-compose logs --tail=0 --follow + yamlFiles=$(select_yaml_files) + services="nginx node tor db" + if [ "$BITCOIND_INSTALL" == "on" ]; then + services="$services bitcoind" + fi + eval "docker-compose $yamlFiles logs --tail=0 --follow $services" ;; esac } @@ -109,6 +227,8 @@ help() { echo " " echo " bitcoin-cli Launch a bitcoin-cli console allowing to interact with your full node through its RPC API." echo " " + echo " clean Free disk space by deleting docker dangling images and images of previous versions." + echo " " echo " install Install your dojo." echo " " echo " logs [module] [options] Display the logs of your dojo. Use CTRL+C to stop the logs." @@ -137,6 +257,10 @@ help() { echo " stop Stop your dojo." echo " " echo " uninstall Delete your dojo. Be careful! This command will also remove all data." + echo " " + echo " upgrade Upgrade your dojo." + echo " " + echo " version Display the version of dojo" } @@ -163,18 +287,25 @@ subcommand=$1; shift case "$subcommand" in bitcoin-cli ) - docker exec -it bitcoind bitcoin-cli \ - -rpcconnect=bitcoind \ - --rpcport=28256 \ - --rpcuser="$BITCOIND_RPC_USER" \ - --rpcpassword="$BITCOIND_RPC_PASSWORD" \ - $1 $2 $3 $4 $5 + if [ "$BITCOIND_INSTALL" == "on" ]; then + docker exec -it bitcoind bitcoin-cli \ + -rpcconnect=bitcoind \ + --rpcport=28256 \ + --rpcuser="$BITCOIND_RPC_USER" \ + --rpcpassword="$BITCOIND_RPC_PASSWORD" \ + $1 $2 $3 $4 $5 + else + echo -e "Command not supported for your setup.\nCause: Your Dojo is using an external bitcoind" + fi ;; help ) help ;; + clean ) + clean + ;; install ) - install + install $1 ;; logs ) module=$1; shift @@ -219,4 +350,10 @@ case "$subcommand" in uninstall ) uninstall ;; + upgrade ) + upgrade $1 + ;; + version ) + version + ;; esac \ No newline at end of file diff --git a/docker/my-dojo/install/install-scripts.sh b/docker/my-dojo/install/install-scripts.sh new file mode 100755 index 0000000..a5067db --- /dev/null +++ b/docker/my-dojo/install/install-scripts.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Confirm installation +get_confirmation() { + while true; do + echo "This operation is going to install Dojo v$DOJO_VERSION_TAG on your computer." + read -p "Do you wish to continue? [y/n]" yn + case $yn in + [Yy]* ) return 0;; + [Nn]* ) echo "Installation was cancelled."; return 1;; + * ) echo "Please answer yes or no.";; + esac + done +} + +# Initialize configuration files from templates +init_config_files() { + cp ../../db-scripts/1_db.sql.tpl ../../db-scripts/1_db.sql + echo "Initialized 1_db.sql" + + if [ -f ../../db-scripts/2_update.sql ]; then + rm ../../db-scripts/2_update.sql + echo "Deleted 2_update.sql" + fi + + cp ./conf/docker-bitcoind.conf.tpl ./conf/docker-bitcoind.conf + echo "Initialized docker-bitcoind.conf" + + cp ./conf/docker-mysql.conf.tpl ./conf/docker-mysql.conf + echo "Initialized docker-mysql.conf" + + cp ./conf/docker-node.conf.tpl ./conf/docker-node.conf + echo "Initialized docker-node.conf" +} diff --git a/docker/my-dojo/install/upgrade-scripts.sh b/docker/my-dojo/install/upgrade-scripts.sh new file mode 100755 index 0000000..757bd6e --- /dev/null +++ b/docker/my-dojo/install/upgrade-scripts.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Confirm upgrade operation +get_confirmation() { + while true; do + echo "This operation is going to upgrade your Dojo to v$DOJO_VERSION_TAG." + read -p "Do you wish to continue? [y/n]" yn + case $yn in + [Yy]* ) return 0;; + [Nn]* ) echo "Upgrade was cancelled."; return 1;; + * ) echo "Please answer yes or no.";; + esac + done +} + +# Update configuration files from templates +update_config_files() { + if [ -f ../../db-scripts/1_db.sql ]; then + rm ../../db-scripts/1_db.sql + echo "Deleted 1_db.sql" + fi + + cp ../../db-scripts/2_update.sql.tpl ../../db-scripts/2_update.sql + echo "Initialized 2_update.sql" + + update_config_file ./conf/docker-bitcoind.conf ./conf/docker-bitcoind.conf.tpl + echo "Initialized docker-bitcoind.conf" + + update_config_file ./conf/docker-mysql.conf ./conf/docker-mysql.conf.tpl + echo "Initialized docker-mysql.conf" + + update_config_file ./conf/docker-node.conf ./conf/docker-node.conf.tpl + echo "Initialized docker-node.conf" +} + +# Update a configuration file from template +update_config_file() { + sed "s/^#.*//g;s/=.*//g;/^$/d" $1 > ./original.keys.raw + grep -f ./original.keys.raw $1 > ./original.lines.raw + + cp -p $1 "$1.save" + cp -p $2 $1 + + while IFS='=' read -r key val ; do + sed -i "s/$key=.*/$key=$val/g" "$1" + done < ./original.lines.raw + + rm ./original.keys.raw + rm ./original.lines.raw +} + +# Update dojo database +update_dojo_db() { + docker exec -d db /update-db.sh +} + +# Clean-up +cleanup() { + ################# + # Clean-up v1.1.0 + ################# + + # Remove deprecated bitcoin.conf file + if [ -f ./bitcoin/bitcoin.conf ]; then + rm ./bitcoin/bitcoin.conf + fi +} diff --git a/docker/my-dojo/mysql/Dockerfile b/docker/my-dojo/mysql/Dockerfile index 4802b8a..c5f70ed 100644 --- a/docker/my-dojo/mysql/Dockerfile +++ b/docker/my-dojo/mysql/Dockerfile @@ -1,7 +1,13 @@ -FROM mysql:5.7.25 +FROM mysql:5.7.25 # Copy mysql config -COPY ./docker/my-dojo/mysql/mysql-dojo.cnf /etc/mysql/conf.d/mysql-dojo.cnf +COPY ./docker/my-dojo/mysql/mysql-dojo.cnf /etc/mysql/conf.d/mysql-dojo.cnf + +# Copy update-db script +COPY ./docker/my-dojo/mysql/update-db.sh /update-db.sh + +RUN chmod u+x /update-db.sh && \ + chmod g+x /update-db.sh # Copy content of mysql scripts into /docker-entrypoint-initdb.d -COPY ./db-scripts/ /docker-entrypoint-initdb.d \ No newline at end of file +COPY ./db-scripts/ /docker-entrypoint-initdb.d \ No newline at end of file diff --git a/docker/my-dojo/mysql/update-db.sh b/docker/my-dojo/mysql/update-db.sh new file mode 100644 index 0000000..001d2d9 --- /dev/null +++ b/docker/my-dojo/mysql/update-db.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +for i in {30..0}; do + if echo "SELECT 1" | mysql -h"db" -u"root" -p"$MYSQL_ROOT_PASSWORD" &> /dev/null; then + break + fi + echo "MySQL init process in progress..." + sleep 1 +done + +if [ -f /docker-entrypoint-initdb.d/2_update.sql ]; then + mysql -h"db" -u"root" -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /docker-entrypoint-initdb.d/2_update.sql + echo "Updated database with 2_update.sql" +fi diff --git a/docker/my-dojo/nginx/dojo.conf b/docker/my-dojo/nginx/dojo.conf index e9aa240..c7517d3 100644 --- a/docker/my-dojo/nginx/dojo.conf +++ b/docker/my-dojo/nginx/dojo.conf @@ -44,6 +44,11 @@ server { proxy_pass http://node:8080/; } + # Redirect onion address to maintenance tool + location = / { + return 301 /admin; + } + # Serve remaining requests location / { return 200 '{"status":"ok"}'; diff --git a/docker/my-dojo/nginx/nginx.conf b/docker/my-dojo/nginx/nginx.conf index ef6e6bc..4985916 100644 --- a/docker/my-dojo/nginx/nginx.conf +++ b/docker/my-dojo/nginx/nginx.conf @@ -24,7 +24,7 @@ http { sendfile on; - keepalive_timeout 65; + keepalive_timeout 95; # Enable response compression gzip on; diff --git a/docker/my-dojo/node/Dockerfile b/docker/my-dojo/node/Dockerfile index 7a73a0d..eb5fbcb 100644 --- a/docker/my-dojo/node/Dockerfile +++ b/docker/my-dojo/node/Dockerfile @@ -20,7 +20,7 @@ COPY . "$APP_DIR" # Install node modules required by the app RUN cd "$APP_DIR" && \ - npm install --only=prod + npm ci --only=production # Copy config file COPY ./docker/my-dojo/node/keys.index.js "$APP_DIR/keys/index.js" diff --git a/docker/my-dojo/node/keys.index.js b/docker/my-dojo/node/keys.index.js index c1831ac..5b365a6 100644 --- a/docker/my-dojo/node/keys.index.js +++ b/docker/my-dojo/node/keys.index.js @@ -27,14 +27,14 @@ module.exports = { // Password pass: process.env.BITCOIND_RPC_PASSWORD, // IP address - host: 'bitcoind', + host: process.env.BITCOIND_IP, // TCP port - port: 28256 + port: parseInt(process.env.BITCOIND_RPC_PORT) }, // ZMQ Tx notifications - zmqTx: 'tcp://bitcoind:9501', + zmqTx: `tcp://${process.env.BITCOIND_IP}:${process.env.BITCOIND_ZMQ_RAWTXS}`, // ZMQ Block notifications - zmqBlk: 'tcp://bitcoind:9502', + zmqBlk: `tcp://${process.env.BITCOIND_IP}:${process.env.BITCOIND_ZMQ_BLK_HASH}`, // Fee type (estimatesmartfee) feeType: process.env.NODE_FEE_TYPE }, diff --git a/docker/my-dojo/overrides/bitcoind.install.yaml b/docker/my-dojo/overrides/bitcoind.install.yaml new file mode 100644 index 0000000..f817139 --- /dev/null +++ b/docker/my-dojo/overrides/bitcoind.install.yaml @@ -0,0 +1,34 @@ +version: "3.2" + +services: + bitcoind: + image: "samouraiwallet/dojo-bitcoind:${DOJO_BITCOIND_VERSION_TAG}" + container_name: bitcoind + build: + context: ./bitcoin + env_file: + - ./.env + - ./conf/docker-bitcoind.conf + restart: on-failure + command: "/wait-for-it.sh tor:9050 --timeout=360 --strict -- /restart.sh" + expose: + - "8333" + - "28256" + - "9501" + - "9502" + volumes: + - data-bitcoind:/home/bitcoin/.bitcoin + - data-tor:/var/lib/tor + depends_on: + - db + - tor + networks: + dojonet: + ipv4_address: 172.28.1.5 + + node: + depends_on: + - bitcoind + +volumes: + data-bitcoind: diff --git a/docker/my-dojo/overrides/bitcoind.rpc.expose.yaml b/docker/my-dojo/overrides/bitcoind.rpc.expose.yaml new file mode 100644 index 0000000..5510711 --- /dev/null +++ b/docker/my-dojo/overrides/bitcoind.rpc.expose.yaml @@ -0,0 +1,10 @@ +version: "3.2" + +services: + bitcoind: + ports: + - "${BITCOIND_RPC_EXTERNAL_IP}:28256:28256" + - "${BITCOIND_RPC_EXTERNAL_IP}:9500:9500" + - "${BITCOIND_RPC_EXTERNAL_IP}:9501:9501" + - "${BITCOIND_RPC_EXTERNAL_IP}:9502:9502" + - "${BITCOIND_RPC_EXTERNAL_IP}:9503:9503" diff --git a/docker/my-dojo/tor/Dockerfile b/docker/my-dojo/tor/Dockerfile index 60cd36d..59578f2 100644 --- a/docker/my-dojo/tor/Dockerfile +++ b/docker/my-dojo/tor/Dockerfile @@ -23,11 +23,6 @@ RUN set -ex && \ RUN addgroup --system -gid 1107 tor && \ adduser --system --ingroup tor -uid 1104 tor -# Create group & user bitcoin and add user to tor group -RUN addgroup --system -gid 1108 bitcoin && \ - adduser --system --ingroup bitcoin -uid 1105 bitcoin && \ - usermod -a -G tor bitcoin - # Create /etc/tor directory RUN mkdir -p /etc/tor/ && \ chown -Rv tor:tor /etc/tor @@ -35,12 +30,19 @@ RUN mkdir -p /etc/tor/ && \ # Create .tor subdirectory of TOR_HOME RUN mkdir -p "$TOR_HOME/.tor" && \ chown -Rv tor:tor "$TOR_HOME" && \ - chmod -R 700 "$TOR_HOME" + chmod -R 750 "$TOR_HOME" # Copy Tor configuration file COPY ./torrc /etc/tor/torrc RUN chown tor:tor /etc/tor/torrc +# Copy restart script +COPY ./restart.sh /restart.sh + +RUN chown tor:tor /restart.sh && \ + chmod u+x /restart.sh && \ + chmod g+x /restart.sh + # Copy wait-for-it script COPY ./wait-for-it.sh /wait-for-it.sh diff --git a/docker/my-dojo/tor/restart.sh b/docker/my-dojo/tor/restart.sh new file mode 100644 index 0000000..24921ba --- /dev/null +++ b/docker/my-dojo/tor/restart.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +# Set permissions for bitcoind +echo "## Set permissions on /var/lib/tor dir ###" +chmod 750 /var/lib/tor + +echo "## Start tor #############################" +tor \ No newline at end of file diff --git a/docker/my-dojo/tor/torrc b/docker/my-dojo/tor/torrc index a04fbdc..30bc02e 100644 --- a/docker/my-dojo/tor/torrc +++ b/docker/my-dojo/tor/torrc @@ -19,17 +19,7 @@ SocksPolicy reject * ## things in $HOME/.tor on Unix, and in Application Data\tor on Windows. DataDirectory /var/lib/tor/.tor - -## The port on which Tor will listen for local connections from Tor -## controller applications, as documented in control-spec.txt. - -ControlPort 9051 - -## If you enable the controlport, be sure to enable one of these -## authentication methods, to prevent attackers from accessing it. - -CookieAuthentication 1 -CookieAuthFileGroupReadable 1 +DataDirectoryGroupReadable 1 ############### This section is just for location-hidden services ### @@ -47,3 +37,8 @@ HiddenServicePort 80 172.29.1.3:80 HiddenServiceDir /var/lib/tor/hsv3dojo HiddenServiceVersion 3 HiddenServicePort 80 172.29.1.3:80 + +HiddenServiceDir /var/lib/tor/hsv2bitcoind +HiddenServiceVersion 2 +HiddenServicePort 8333 172.28.1.5:8333 +HiddenServiceDirGroupReadable 1 diff --git a/keys/index-example.js b/keys/index-example.js index d9647c6..e843b45 100644 --- a/keys/index-example.js +++ b/keys/index-example.js @@ -15,7 +15,7 @@ module.exports = { /* * Dojo version */ - dojoVersion: '1.0.0', + dojoVersion: '1.1.0', /* * Bitcoind */ diff --git a/lib/db/mysql-db-wrapper.js b/lib/db/mysql-db-wrapper.js index 6171341..dcab303 100644 --- a/lib/db/mysql-db-wrapper.js +++ b/lib/db/mysql-db-wrapper.js @@ -931,6 +931,36 @@ class MySqlDbWrapper { return ret } + /** + * Get the number of indices derived in an interval for a HD chain + * @param {string} xpub - xpub + * @param {integer} chain - HD chain (0 or 1) + * @param {integer} minIdx - min index of derivation + * @param {integer} maxIdx - max index of derivation + * @returns {integer[]} returns an array of number of derived indices + */ + async getHDAccountNbDerivedIndices(xpub, chain, minIdx, maxIdx) { + const sqlQuery = 'SELECT \ + COUNT(`hd_addresses`.`hdAddrIndex`) AS `nbDerivedIndices` \ + FROM `hd_addresses` \ + INNER JOIN `hd` ON `hd_addresses`.`hdID` = `hd`.`hdID` \ + WHERE `hd`.`hdXpub` = ? \ + AND `hd_addresses`.`hdAddrChain` = ? \ + AND `hd_addresses`.`hdAddrIndex` >= ? \ + AND `hd_addresses`.`hdAddrIndex` <= ?' + + const params = [xpub, chain, minIdx, maxIdx] + const query = mysql.format(sqlQuery, params) + const results = await this._query(query) + + if (results.length == 1) { + const nbDerivedIndices = results[0].nbDerivedIndices + return (nbDerivedIndices == null) ? 0 : nbDerivedIndices + } + + return 0 + } + /** * Get the number of transactions for an HD account * @param {string} xpub - xpub diff --git a/lib/remote-importer/btccom-wrapper.js b/lib/remote-importer/btccom-wrapper.js index cc86d38..767f4b5 100644 --- a/lib/remote-importer/btccom-wrapper.js +++ b/lib/remote-importer/btccom-wrapper.js @@ -66,11 +66,11 @@ class BtcComWrapper extends Wrapper { * { address: , txids: , ntx: } */ async getAddress(address, filterAddr) { - // Extracts the scripthash from the bech32 address - // (btc.com api manages scripthashes, not bech32 addresses) - const scripthash = addrHelper.getScriptHashFromBech32(address) + const reqAddr = addrHelper.isBech32(address) + ? addrHelper.getScriptHashFromBech32(address) + : address - const uri = `/address/${scripthash}` + const uri = `/address/${reqAddr}` const result = await this._get(uri) const ret = { @@ -92,7 +92,7 @@ class BtcComWrapper extends Wrapper { const listPages = Array.from(aPages, (val, idx) => idx + 1) const results = await util.seriesCall(listPages, idx => { - return this._getTxsForAddress(scripthash, idx) + return this._getTxsForAddress(reqAddr, idx) }) for (let txids of results) @@ -103,15 +103,72 @@ class BtcComWrapper extends Wrapper { /** * Retrieve information for a given list of addresses - * @param {string} addresses - array of bitcoin addresses + * @param {string[]} addresses - array of bitcoin addresses * @param {boolean} filterAddr - True if an upper bound should be used * for #transactions associated to the address, False otherwise * @returns {Promise} returns an array of objects * { address: , txids: , ntx: } */ async getAddresses(addresses, filterAddr) { - // Not implemented for this api - throw "Not implemented" + const ret = [] + const reqAddresses = [] + const xlatedBech32Addr = {} + + for (let a of addresses) { + if (addrHelper.isBech32(a)) { + const scriptHash = addrHelper.getScriptHashFromBech32(a) + reqAddresses.push(scriptHash) + xlatedBech32Addr[scriptHash] = a + } else { + reqAddresses.push(a) + } + } + + // Send a batch request for all the addresses + const strReqAddresses = reqAddresses.join(',') + const uri = `/address/${strReqAddresses}` + const results = await this._get(uri) + + const foundAddresses = Array.isArray(results.data) + ? results.data + : [results.data] + + for (let a of foundAddresses) { + if (a && a.tx_count > 0) { + // Translate bech32 address + const address = xlatedBech32Addr.hasOwnProperty(a.address) + ? xlatedBech32Addr[a.address] + : a.address + + if (a.tx_count <= 2) { + // Less than 3 transactions for this address + // all good + const retAddr = { + address: address, + ntx: a.tx_count, + txids: [] + } + + retAddr.txids = (a.tx_count == 1) + ? [a.first_tx] + : [a.first_tx, a.last_tx] + + ret.push(retAddr) + + } else { + // More than 2 transactions for this address + // We need more requests to the API + if (filterAddr && a.tx_count > keys.addrFilterThreshold) { + Logger.info(` import of ${address} rejected (too many transactions - ${a.tx_count})`) + } else { + const retAddr = await this.getAddress(address) + ret.push(retAddr) + } + } + } + } + + return ret } } diff --git a/lib/remote-importer/sources-mainnet.js b/lib/remote-importer/sources-mainnet.js index 64bb932..6bc1121 100644 --- a/lib/remote-importer/sources-mainnet.js +++ b/lib/remote-importer/sources-mainnet.js @@ -4,9 +4,7 @@ */ 'use strict' -const addrHelper = require('../bitcoin/addresses-helper') const network = require('../bitcoin/network') -const util = require('../util') const Logger = require('../logger') const keys = require('../../keys')[network.key] const Sources = require('./sources') @@ -24,8 +22,6 @@ class SourcesMainnet extends Sources { */ constructor() { super() - // Initializes external source - this.source = null this._initSource() } @@ -45,68 +41,6 @@ class SourcesMainnet extends Sources { } } - /** - * Retrieve information for a given address - * @param {string} address - bitcoin address - * @param {boolean} filterAddr - True if an upper bound should be used - * for #transactions associated to the address, False otherwise - * @returns {Promise} returns an object - * { address: , txids: , ntx: } - */ - async getAddress(address, filterAddr) { - const ret = { - address, - txids: [], - ntx: 0 - } - - try { - const result = await this.source.getAddress(address, filterAddr) - - if (result.ntx) - ret.ntx = result.ntx - else if (result.txids) - ret.ntx = result.txids.length - - if (result.txids) - ret.txids = result.txids - - } catch(e) { - //Logger.error(e, `SourcesMainnet.getAddress() : ${address} from ${this.source.base}`) - Logger.error(null, `SourcesMainnet.getAddress() : ${address} from ${this.source.base}`) - } finally { - return ret - } - } - - /** - * Retrieve information for a list of addresses - * @param {string[]} addresses - array of bitcoin address - * @param {boolean} filterAddr - True if an upper bound should be used - * for #transactions associated to the address, False otherwise - * @returns {Promise} returns an object - * { address: , txids: , ntx: } - */ - async getAddresses(addresses, filterAddr) { - const ret = [] - - try { - const results = await this.source.getAddresses(addresses, filterAddr) - - for (let r of results) { - // Filter addresses with too many txs - if (!filterAddr || (r.ntx <= keys.addrFilterThreshold)) - ret.push(r) - } - - } catch(e) { - //Logger.error(e, `SourcesMainnet.getAddresses() : ${addresses} from ${this.source.base}`) - Logger.error(null, `SourcesMainnet.getAddresses() : ${addresses} from ${this.source.base}`) - } finally { - return ret - } - } - } module.exports = SourcesMainnet diff --git a/lib/remote-importer/sources-testnet.js b/lib/remote-importer/sources-testnet.js index 2dfbd01..588f9e0 100644 --- a/lib/remote-importer/sources-testnet.js +++ b/lib/remote-importer/sources-testnet.js @@ -4,19 +4,17 @@ */ 'use strict' -const addrHelper = require('../bitcoin/addresses-helper') const network = require('../bitcoin/network') const util = require('../util') const Logger = require('../logger') const keys = require('../../keys')[network.key] const Sources = require('./sources') const BitcoindWrapper = require('./bitcoind-wrapper') -const InsightWrapper = require('./insight-wrapper') const BtcComWrapper = require('./btccom-wrapper') /** - * Remote data sources for testnet polled round-robin to spread load + * Remote data sources for testnet */ class SourcesTestnet extends Sources { @@ -25,126 +23,22 @@ class SourcesTestnet extends Sources { */ constructor() { super() - this.sources = [] - this.index = 0 - this.sourceBech32 = null - this.isBitcoindActive = false - // Initializes external sources - this._initSources() + this._initSource() } /** - * Initialize the external data sources + * Initialize the external data source */ - _initSources() { + _initSource() { if (keys.explorers.bitcoind == 'active') { // If local bitcoind option is activated // we'll use the local node as our unique source - this.sourceBech32 = new BitcoindWrapper() - this.sources.push(this.sourceBech32) - this.isBitcoindActive = true + this.source = new BitcoindWrapper() + Logger.info('Activated Bitcoind as the data source for imports') } else { - // Otherwise, we use a set of insight servers + btc.com for bech32 addresses - this.sourceBech32 = new BtcComWrapper(keys.explorers.btccom) - for (let url of keys.explorers.insight) - this.sources.push(new InsightWrapper(url)) - this.isBitcoindActive = false - } - } - - /** - * Get the next source index - * @returns {integer} returns the next source index - */ - nextIndex() { - this.index++ - if (this.index >= this.sources.length) - this.index = 0 - return this.index - } - - /** - * Retrieve information for a given address - * @param {string} address - bitcoin address - * @param {boolean} filterAddr - True if an upper bound should be used - * for #transactions associated to the address, False otherwise - * @returns {Promise} returns an object - * { address: , txids: , ntx: } - */ - async getAddress(address, filterAddr) { - let source = '' - - const isBech32 = addrHelper.isBech32(address) - - const ret = { - address, - txids: [], - ntx: 0 - } - - try { - source = isBech32 ? this.sourceBech32 : this.sources[this.nextIndex()] - const result = await source.getAddress(address, filterAddr) - - if (result.ntx) - ret.ntx = result.ntx - else if (result.txids) - ret.ntx = result.txids.length - - if (result.txids) - ret.txids = result.txids - - return ret - - } catch(e) { - Logger.error(e, `SourcesTestnet.getAddress() : ${address} from ${source.base}`) - if (!isBech32 && this.sources.length > 1) { - // Try again with another source - return this.getAddress(address, filterAddr) - } else { - return ret - } - } - } - - /** - * Retrieve information for a list of addresses - * @param {string[]} addresses - array of bitcoin address - * @param {boolean} filterAddr - True if an upper bound should be used - * for #transactions associated to the address, False otherwise - * @returns {Promise} returns an object - * { address: , txids: , ntx: } - */ - async getAddresses(addresses, filterAddr) { - const ret = [] - - try { - if (this.isBitcoindActive) { - const source = this.sources[0] - const results = await source.getAddresses(addresses, filterAddr) - for (let r of results) { - // Filter addresses with too many txs - if (!filterAddr || (r.ntx <= keys.addrFilterThreshold)) - ret.push(r) - } - } else { - const lists = util.splitList(addresses, this.sources.length) - await util.seriesCall(lists, async list => { - const results = await Promise.all(list.map(a => { - return this.getAddress(a, filterAddr) - })) - - for (let r of results) { - // Filter addresses with too many txs - if (!filterAddr || (r.ntx <= keys.addrFilterThreshold)) - ret.push(r) - } - }) - } - } catch (e) { - Logger.error(e, `SourcesTestnet.getAddresses() : Addr list = ${addresses}`) - } finally { - return ret + // Otherwise, we'll use the rest api provided by OXT + this.source = new BtcComWrapper(keys.explorers.btccom) + Logger.info('Activated BTC.COM API as the data source for imports') } } diff --git a/lib/remote-importer/sources.js b/lib/remote-importer/sources.js index f070c8a..9f2d7d6 100644 --- a/lib/remote-importer/sources.js +++ b/lib/remote-importer/sources.js @@ -4,16 +4,22 @@ */ 'use strict' +const network = require('../bitcoin/network') +const Logger = require('../logger') +const keys = require('../../keys')[network.key] + /** - * Abstract class defining a list of blockchain explorer providing a remote API + * Base class defining data source for imports/rescans of HD accounts and addresses */ class Sources { /** * Constructor */ - constructor() {} + constructor() { + this.source = null + } /** * Retrieve information for a given address @@ -23,7 +29,30 @@ class Sources { * @returns {Promise} returns an object * { address: , txids: , ntx: } */ - async getAddress(address, filterAddr) {} + async getAddress(address, filterAddr) { + const ret = { + address, + txids: [], + ntx: 0 + } + + try { + const result = await this.source.getAddress(address, filterAddr) + + if (result.ntx) + ret.ntx = result.ntx + else if (result.txids) + ret.ntx = result.txids.length + + if (result.txids) + ret.txids = result.txids + + } catch(e) { + Logger.error(null, `Sources.getAddress() : ${address} from ${this.source.base}`) + } finally { + return ret + } + } /** * Retrieve information for a list of addresses @@ -33,7 +62,24 @@ class Sources { * @returns {Promise} returns an object * { address: , txids: , ntx: } */ - async getAddresses(addresses, filterAddr) {} + async getAddresses(addresses, filterAddr) { + const ret = [] + + try { + const results = await this.source.getAddresses(addresses, filterAddr) + + for (let r of results) { + // Filter addresses with too many txs + if (!filterAddr || (r.ntx <= keys.addrFilterThreshold)) + ret.push(r) + } + + } catch(e) { + Logger.error(e, `Sources.getAddresses() : ${addresses} from ${this.source.base}`) + } finally { + return ret + } + } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3f6076a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2302 @@ +{ + "name": "samourai-dojo", + "version": "1.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "2.1.24", + "negotiator": "0.6.2" + } + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "requires": { + "es6-promisify": "5.0.0" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "async-sema": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-2.1.2.tgz", + "integrity": "sha512-7Wxs4QBLL9XklTVaRKH16ws/YJBZ7wouqnSu84jILDqZZObsrhDfsVELhQE7U53b2qkFtsAX5ED1ZMvrWzv1Yg==", + "requires": { + "double-ended-queue": "2.1.0-0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "babel-runtime": { + "version": "5.8.38", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz", + "integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=", + "requires": { + "core-js": "1.2.7" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base-x": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.6.tgz", + "integrity": "sha512-4PaF8u2+AlViJxRVjurkLTxpp7CaFRD/jo5rPT9ONnKxyhQ8f59yzamEvq7EkriG56yn5On4ONyaG75HLqr46w==", + "requires": { + "safe-buffer": "5.2.0" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bech32": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-0.0.3.tgz", + "integrity": "sha512-O+K1w8P/aAOLcYwwQ4sbiPYZ51ZIW95lnS4/6nE8Aib/z+OOddQIIPdu2qi94qGDp4HhYy/wJotttXKkak1lXg==" + }, + "bigi": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", + "integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU=" + }, + "bignumber.js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bip39": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-2.4.0.tgz", + "integrity": "sha512-1++HywqIyPtWDo7gm4v0ylYbwkLvHkuwVSKbBlZBbTCP/mnkyrlARBny906VLAwxJbC5xw9EvuJasHFIZaIFMQ==", + "requires": { + "create-hash": "1.2.0", + "pbkdf2": "3.0.17", + "randombytes": "2.1.0", + "safe-buffer": "5.2.0", + "unorm": "1.6.0" + } + }, + "bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", + "requires": { + "safe-buffer": "5.2.0" + } + }, + "bitcoin-ops": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", + "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" + }, + "bitcoind-rpc-client": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bitcoind-rpc-client/-/bitcoind-rpc-client-0.3.1.tgz", + "integrity": "sha1-bKiVn5H/f+O9I7f7XTAqnAKu/o0=", + "requires": { + "babel-runtime": "5.8.38", + "make-concurrent": "1.2.0", + "promise-useful-utils": "0.2.1" + } + }, + "bitcoinjs-lib": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-3.2.0.tgz", + "integrity": "sha512-6as6LSz/K5WbCLi8wTNxPJcQgpgeuTsfS52v/C/dM7IcmnDA5PDaZArhsUTFaKsM6NPFgsQr2ZCbWTjYyXJevw==", + "requires": { + "bech32": "0.0.3", + "bigi": "1.4.2", + "bip66": "1.1.5", + "bitcoin-ops": "1.4.1", + "bs58check": "2.1.2", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ecurve": "1.0.6", + "merkle-lib": "2.0.10", + "pushdata-bitcoin": "1.0.1", + "randombytes": "2.1.0", + "safe-buffer": "5.2.0", + "typeforce": "1.18.0", + "varuint-bitcoin": "1.1.0", + "wif": "2.0.6" + } + }, + "bitcoinjs-message": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-1.0.1.tgz", + "integrity": "sha1-P8xfHYX53TCsYNERlIyxs7T+nMU=", + "requires": { + "bs58check": "1.3.4", + "buffer-equals": "1.0.4", + "create-hash": "1.2.0", + "secp256k1": "3.7.1", + "varuint-bitcoin": "1.1.0" + }, + "dependencies": { + "base-x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-1.1.0.tgz", + "integrity": "sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w=" + }, + "bs58": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-3.1.0.tgz", + "integrity": "sha1-1MJjiL9IBMrHFBQbGUWqR+XrJI4=", + "requires": { + "base-x": "1.1.0" + } + }, + "bs58check": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-1.3.4.tgz", + "integrity": "sha1-xSVABzdJEXcU+gQsMEfrj5FRy/g=", + "requires": { + "bs58": "3.1.0", + "create-hash": "1.2.0" + } + } + } + }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "2.3.6", + "safe-buffer": "5.2.0" + } + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "1.6.18" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.4", + "safe-buffer": "5.2.0" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "3.0.6" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "4.0.1", + "create-hash": "1.2.0", + "safe-buffer": "5.2.0" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-equals": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", + "integrity": "sha1-A1O1T9B/2VZBcGca5vZrnPENJ/U=" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "2.0.4", + "safe-buffer": "5.2.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-security-policy-builder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.0.0.tgz", + "integrity": "sha512-j+Nhmj1yfZAikJLImCvPJFE29x/UuBi+/MWqggGGc515JKaZrjuei2RhULJmy0MsstW3E3htl002bwmBNMKr7w==" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "1.0.4", + "inherits": "2.0.4", + "md5.js": "1.3.5", + "ripemd160": "2.0.2", + "sha.js": "2.4.11" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "inherits": "2.0.4", + "ripemd160": "2.0.2", + "safe-buffer": "5.2.0", + "sha.js": "2.4.11" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "1.0.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "dns-prefetch-control": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", + "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" + }, + "dont-sniff-mimetype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", + "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" + }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, + "drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", + "requires": { + "browserify-aes": "1.2.0", + "create-hash": "1.2.0", + "create-hmac": "1.1.7" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "5.2.0" + } + }, + "ecurve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", + "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", + "requires": { + "bigi": "1.4.2", + "safe-buffer": "5.2.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "elliptic": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", + "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.7", + "hmac-drbg": "1.0.1", + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "1.4.0" + } + }, + "error-system": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/error-system/-/error-system-1.0.1.tgz", + "integrity": "sha1-BxU79HecB5Dnpg7QXvn0fRMYagI=", + "requires": { + "inherits": "2.0.4" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "4.2.8" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "1.3.5", + "safe-buffer": "5.2.0" + } + }, + "expand-template": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", + "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==" + }, + "expect-ct": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.1.1.tgz", + "integrity": "sha512-ngXzTfoRGG7fYens3/RMb6yYoVLvLMfmsSllP/mZPxNHgFq41TmPSLF/nLY7fwoclI2vElvAmILFWGUYqdjfCg==" + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.5", + "qs": "6.5.1", + "range-parser": "1.2.1", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "1.4.0", + "type-is": "1.6.18", + "utils-merge": "1.0.1", + "vary": "1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.3", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.18" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.4.0" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "express-jwt": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", + "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", + "requires": { + "async": "1.5.2", + "express-unless": "0.3.1", + "jsonwebtoken": "8.5.1", + "lodash.set": "4.3.2" + } + }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.3", + "statuses": "1.4.0", + "unpipe": "1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.24" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "frameguard": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.0.0.tgz", + "integrity": "sha1-e8rUae57lukdEs6zlZx4I1qScuk=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + } + }, + "generic-pool": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", + "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "6.10.2", + "har-schema": "2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "2.0.4", + "safe-buffer": "5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "heapdump": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", + "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" + }, + "helmet": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.12.1.tgz", + "integrity": "sha512-/CsAcbPIHgiGde395IkHUZyRLW126RJ6AtxFy6Y6bxhd44Qq8cZ5BBFZ0xNUSbcgX57j32Emh3OhWz/0XgAB5Q==", + "requires": { + "dns-prefetch-control": "0.1.0", + "dont-sniff-mimetype": "1.0.0", + "expect-ct": "0.1.1", + "frameguard": "3.0.0", + "helmet-csp": "2.7.0", + "hide-powered-by": "1.0.0", + "hpkp": "2.0.0", + "hsts": "2.1.0", + "ienoopen": "1.0.0", + "nocache": "2.0.0", + "referrer-policy": "1.1.0", + "x-xss-protection": "1.1.0" + } + }, + "helmet-csp": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.7.0.tgz", + "integrity": "sha512-IGIAkWnxjRbgMXFA2/kmDqSIrIaSfZ6vhMHlSHw7jm7Gm9nVVXqwJ2B1YEpYrJsLrqY+w2Bbimk7snux9+sZAw==", + "requires": { + "camelize": "1.0.0", + "content-security-policy-builder": "2.0.0", + "dasherize": "2.0.0", + "lodash.reduce": "4.6.0", + "platform": "1.3.5" + } + }, + "hide-powered-by": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", + "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "1.1.7", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hsts": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.1.0.tgz", + "integrity": "sha512-zXhh/DqgrTXJ7erTN6Fh5k/xjMhDGXCqdYN3wvxUvGUQvnxcFfUd8E+6vLg/nk3ss1TYMb+DhRl25fYABioTvA==" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": "1.5.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ienoopen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.0.0.tgz", + "integrity": "sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "3.2.2", + "lodash.includes": "4.3.0", + "lodash.isboolean": "3.0.3", + "lodash.isinteger": "4.0.4", + "lodash.isnumber": "3.0.3", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.once": "4.1.1", + "ms": "2.1.2", + "semver": "5.7.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "5.2.0" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "1.4.1", + "safe-buffer": "5.2.0" + } + }, + "lodash": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "make-concurrent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/make-concurrent/-/make-concurrent-1.2.0.tgz", + "integrity": "sha1-2XfixWy4hXfCyDlONAzZ7rJc80Y=", + "requires": { + "babel-runtime": "5.8.38" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.4", + "safe-buffer": "5.2.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merkle-lib": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz", + "integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mysql": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.16.0.tgz", + "integrity": "sha512-dPbN2LHonQp7D5ja5DJXNbCLe/HRdu+f3v61aguzNRQIrmZLOeRoymBYyeThrR6ug+FqzDL95Gc9maqZUJS+Gw==", + "requires": { + "bignumber.js": "4.1.0", + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "nocache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz", + "integrity": "sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA=" + }, + "node-abi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz", + "integrity": "sha512-jmEOvv0eanWjhX8dX1pmjb7oJl1U1oR4FOh0b2GnvALwSYoOdU7sj+kLDSAyjo4pfC9aj/IxkloxdLJQhSSQBA==", + "requires": { + "semver": "5.7.0" + } + }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "passport": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz", + "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=", + "requires": { + "passport-strategy": "1.0.0", + "pause": "0.0.1" + } + }, + "passport-localapikey-update": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport-localapikey-update/-/passport-localapikey-update-0.6.0.tgz", + "integrity": "sha512-NklCLY68AdepFID+HQ9CVPqRKKYIPw+fGQBsQoP/WZ2ovVfmJ0Qx7eoBpUtpta0vyav2TPDoHSAKoHTjO+LPcw==", + "requires": { + "passport-strategy": "1.0.0", + "pkginfo": "0.2.3" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "requires": { + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.2.0", + "sha.js": "2.4.11" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pkginfo": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz", + "integrity": "sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg=" + }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" + }, + "prebuild-install": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", + "integrity": "sha512-/rI36cN2g7vDQnKWN8Uzupi++KjyqS9iS+/fpwG4Ea8d0Pip0PQ5bshUNzVwt+/D2MRfhVAplYMMvWLqWrCF/g==", + "requires": { + "detect-libc": "1.0.3", + "expand-template": "1.1.1", + "github-from-package": "0.0.0", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "node-abi": "2.9.0", + "noop-logger": "0.1.1", + "npmlog": "4.1.2", + "os-homedir": "1.0.2", + "pump": "2.0.1", + "rc": "1.2.8", + "simple-get": "2.8.1", + "tar-fs": "1.16.3", + "tunnel-agent": "0.6.0", + "which-pm-runs": "1.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise-useful-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/promise-useful-utils/-/promise-useful-utils-0.2.1.tgz", + "integrity": "sha1-pDgNGJnia/2kNB1hD4LKCOQmLqw=", + "requires": { + "babel-runtime": "5.8.38", + "error-system": "1.0.1" + } + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "pushdata-bitcoin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", + "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", + "requires": { + "bitcoin-ops": "1.4.1" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "5.2.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "referrer-policy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz", + "integrity": "sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk=" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.24", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.2.0", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "requires": { + "lodash": "4.17.14" + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.4.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.4" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "secp256k1": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz", + "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==", + "requires": { + "bindings": "1.5.0", + "bip66": "1.1.5", + "bn.js": "4.11.8", + "create-hash": "1.2.0", + "drbg.js": "1.0.1", + "elliptic": "6.5.0", + "nan": "2.14.0", + "safe-buffer": "5.2.0" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.3", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.1", + "statuses": "1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.3", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "2.0.4", + "safe-buffer": "5.2.0" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "simple-get": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "requires": { + "decompress-response": "3.3.0", + "once": "1.4.0", + "simple-concat": "1.0.0" + } + }, + "smart-buffer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==" + }, + "socks": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.3.tgz", + "integrity": "sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA==", + "requires": { + "ip": "1.1.5", + "smart-buffer": "4.0.2" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "requires": { + "agent-base": "4.2.1", + "socks": "2.2.3" + } + }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + }, + "tar-fs": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "requires": { + "chownr": "1.1.2", + "mkdirp": "0.5.1", + "pump": "1.0.3", + "tar-stream": "1.6.2" + }, + "dependencies": { + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "requires": { + "bl": "1.2.2", + "buffer-alloc": "1.2.0", + "end-of-stream": "1.4.1", + "fs-constants": "1.0.0", + "readable-stream": "2.3.6", + "to-buffer": "1.1.1", + "xtend": "4.0.2" + } + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.2.0", + "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.2.0" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "1.0.0" + } + }, + "typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "2.1.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "validator": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.8.0.tgz", + "integrity": "sha512-mXqMxfCh5NLsVgYVKl9WvnHNDPCcbNppHSPPowu0VjtSsGWVY+z8hJF44edLR1nbLNzi3jYoYsIl8KZpioIk6g==" + }, + "varuint-bitcoin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz", + "integrity": "sha512-jCEPG+COU/1Rp84neKTyDJQr478/hAfVp5xxYn09QEH0yBjbmPeMfuuQIrp+BUD83hybtYZKhr5elV3bvdV1bA==", + "requires": { + "safe-buffer": "5.2.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "websocket": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.28.tgz", + "integrity": "sha512-00y/20/80P7H4bCYkzuuvvfDvh+dgtXi5kzDf3UcZwN6boTYaKvsrtZ5lIYm1Gsg48siMErd9M4zjSYfYFHTrA==", + "requires": { + "debug": "2.6.9", + "nan": "2.14.0", + "typedarray-to-buffer": "3.1.5", + "yaeti": "0.0.6" + } + }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "1.0.2" + } + }, + "wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "requires": { + "bs58check": "2.1.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "x-xss-protection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.1.0.tgz", + "integrity": "sha512-rx3GzJlgEeZ08MIcDsU2vY2B1QEriUKJTSiNHHUIem6eg9pzVOr2TL3Y4Pd6TMAM5D5azGjcxqI62piITBDHVg==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "zeromq": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-4.2.0.tgz", + "integrity": "sha1-4RMzBd9zyE+MffBgqOKzA7THLX8=", + "requires": { + "nan": "2.14.0", + "prebuild-install": "2.5.3" + } + } + } +} diff --git a/package.json b/package.json index 8109687..1829c84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "samourai-dojo", - "version": "1.0.0", + "version": "1.1.0", "description": "Backend server for Samourai Wallet", "main": "accounts/index.js", "scripts": { @@ -25,6 +25,7 @@ "generic-pool": "3.4.2", "heapdump": "0.3.9", "helmet": "3.12.1", + "lodash": "4.17.14", "lru-cache": "4.0.2", "mysql": "2.16.0", "passport": "0.4.0", diff --git a/pushtx/orchestrator.js b/pushtx/orchestrator.js index 0a8c779..22d1090 100644 --- a/pushtx/orchestrator.js +++ b/pushtx/orchestrator.js @@ -82,16 +82,21 @@ class Orchestrator { Logger.info(`Block ${height} ${blockHash}`) - // Retrieve the transactions triggered by this block - let txs = await db.getActivatedScheduledTransactions(height) - - while (txs && txs.length > 0) { - let rpcConnOk = true + let nbTxsPushed + let rpcConnOk = true + + do { + nbTxsPushed = 0 + + // Retrieve the transactions triggered by this block + let txs = await db.getActivatedScheduledTransactions(height) + if (!(txs && txs.length > 0)) + break for (let tx of txs) { let hasParentTx = (tx.schParentTxid != null) && (tx.schParentTxid != '') let parentTx = null - + // Check if previous transaction has been confirmed if (hasParentTx) { try { @@ -132,20 +137,15 @@ class Orchestrator { // Delete the transaction try { await db.deleteScheduledTransaction(tx.schTxid) + // Count the transaction as successfully processed + nbTxsPushed++ } catch(e) { const msg = 'A problem was met while trying to delete a scheduled transaction' Logger.error(e, `Orchestrator.onBlockHash() : ${msg}`) } } } - - // If a connection issue was detected, then stop the loop - if (!rpcConnOk) - break - - // Check if more transactions have to be pushed - txs = await db.getActivatedScheduledTransactions(height) - } + } while (rpcConnOk && nbTxsPushed > 0) } catch(e) { Logger.error(e, 'Orchestrator.onBlockHash() : Error') diff --git a/static/admin/index.js b/static/admin/index.js index 58a8519..75eff0e 100644 --- a/static/admin/index.js +++ b/static/admin/index.js @@ -9,7 +9,7 @@ function login() { // Checks input fields if (!apiKey) { - lib_msg.displayErrors('API key is mandatory'); + lib_msg.displayErrors('Admin key is mandatory'); return; } @@ -20,13 +20,17 @@ function login() { function (result) { const auth = result['authorizations']; const accessToken = auth['access_token']; - lib_auth.setAccessToken(accessToken); - const refreshToken = auth['refresh_token']; - lib_auth.setRefreshToken(refreshToken); - sessionStorage.setItem('activeTab', ''); - lib_msg.displayInfo('Successfully connected to your backend'); - // Redirection to default page - lib_cmn.goToDefaultPage(); + if (lib_auth.isAdmin(accessToken)) { + lib_auth.setAccessToken(accessToken); + const refreshToken = auth['refresh_token']; + lib_auth.setRefreshToken(refreshToken); + sessionStorage.setItem('activeTab', ''); + lib_msg.displayInfo('Successfully connected to your backend'); + // Redirection to default page + lib_cmn.goToDefaultPage(); + } else { + lib_msg.displayErrors('You must sign in with the admin key'); + } }, function (jqxhr) { let msg = lib_msg.extractJqxhrErrorMsg(jqxhr); diff --git a/static/admin/lib/auth-utils.js b/static/admin/lib/auth-utils.js index c087418..f0aa818 100644 --- a/static/admin/lib/auth-utils.js +++ b/static/admin/lib/auth-utils.js @@ -12,6 +12,9 @@ var lib_auth = { /* JWT Scheme */ JWT_SCHEME: 'Bearer', + /* Admin profile */ + TOKEN_PROFILE_ADMIN: 'admin', + /* * Retrieves access token from session storage @@ -87,6 +90,36 @@ var lib_auth = { return (token && (token != 'null')) ? true : false; }, + /* + * Extract the payload of an access token + * in json format + */ + getPayloadAccessToken: function(token) { + if (!token) + token = this.getAccessToken(); + + if (!token) + return null; + + try { + const payloadBase64 = token.split('.')[1]; + const payloadUtf8 = atob(payloadBase64); + return JSON.parse(payloadUtf8); + } catch { + return null; + } + }, + + /* + * Check if user has admin profile + */ + isAdmin: function(token) { + const payload = this.getPayloadAccessToken(token); + if (!payload) + return false; + return (('prf' in payload) && (payload['prf'] == this.TOKEN_PROFILE_ADMIN)); + }, + /* * Local logout */ diff --git a/test/lib/bitcoin/addresses-helper-test.js b/test/lib/bitcoin/addresses-helper-test.js index b59b798..7dadc13 100644 --- a/test/lib/bitcoin/addresses-helper-test.js +++ b/test/lib/bitcoin/addresses-helper-test.js @@ -80,21 +80,21 @@ const VECTOR_5 = [ describe('AddressesHelper', function() { - + describe('p2pkhAddress()', function() { it('should successfully derive P2PKH addresses from pubkeys', function() { for (const v of VECTOR_1) { - const pkb = new Buffer(v[0], 'hex') + const pkb = Buffer.from(v[0], 'hex') const addr = addrHelper.p2pkhAddress(pkb) assert(addr == v[1]) } }) }) - + describe('p2wpkhP2shAddress()', function() { it('should successfully derive P2WPKH-P2SH addresses from pubkeys', function() { for (const v of VECTOR_1) { - const pkb = new Buffer(v[0], 'hex') + const pkb = Buffer.from(v[0], 'hex') const addr = addrHelper.p2wpkhP2shAddress(pkb) assert(addr == v[2]) } @@ -104,7 +104,7 @@ describe('AddressesHelper', function() { describe('p2wpkhAddress()', function() { it('should successfully derive bech32 addresses from pubkeys', function() { for (const v of VECTOR_1) { - const pkb = new Buffer(v[0], 'hex') + const pkb = Buffer.from(v[0], 'hex') const addr = addrHelper.p2wpkhAddress(pkb) assert(addr == v[3]) } @@ -150,7 +150,7 @@ describe('AddressesHelper', function() { const expectedResult = stc[2] const sig = btcMessage.sign(msg, prefix, privKey, true) - + // Check that library returns valid result assert((sig.compare(targetSig) == 0) == expectedResult) diff --git a/tracker/blockchain-processor.js b/tracker/blockchain-processor.js index e1266f7..a26a0cd 100644 --- a/tracker/blockchain-processor.js +++ b/tracker/blockchain-processor.js @@ -55,11 +55,9 @@ class BlockchainProcessor extends AbstractProcessor { * @returns {Promise} */ async catchup() { - // Get highest block in the blockchain - const info = await this.client.getblockchaininfo() - - // Consider that we are in IBD mode if bitcoind is far in the past - this.isIBD = info.initialblockdownload && (info.blocks < 550000) + // Consider that we are in IBD mode if Dojo is far in the past + const highest = await db.getHighestBlock() + this.isIBD = highest.blockHeight < 570000 if (this.isIBD) return this.catchupIBDMode() diff --git a/tracker/transaction.js b/tracker/transaction.js index e313227..4d381f9 100644 --- a/tracker/transaction.js +++ b/tracker/transaction.js @@ -333,9 +333,24 @@ class Transaction { if (chainMaxUsedIndex < unusedIndices[chain]) continue - // If max derived index is beyond max used index plus gap limit, move on - if (derivedIndices[chain] >= chainMaxUsedIndex + gapLimit[chain]) - continue + // If max derived index is beyond max used index plus gap limit. + if (derivedIndices[chain] >= chainMaxUsedIndex + gapLimit[chain]) { + // Check that we don't have a hole in the next indices + const nbDerivedIndicesForward = await db.getHDAccountNbDerivedIndices( + xpub, + chain, + chainMaxUsedIndex, + chainMaxUsedIndex + gapLimit[chain] + ) + + if (nbDerivedIndicesForward < gapLimit[chain] + 1) { + // Hole detected. Force derivation. + derivedIndices[chain] = chainMaxUsedIndex + } else { + // Move on + continue + } + } let done