diff --git a/docs/Consensus/Consensus.md b/docs/Consensus/Consensus.md new file mode 100644 index 000000000..fa1b2871f --- /dev/null +++ b/docs/Consensus/Consensus.md @@ -0,0 +1,11 @@ + +With no need for POW/POS in a permissioned network, Quorum instead offers multiple consensus mechanisms that are more appropriate for consortium chains: + + +* __Raft-based Consensus__: A consensus model for faster blocktimes, transaction finality, and on-demand block creation. See [Raft-based consensus for Ethereum/Quorum](../raft) for more information + + +* __Istanbul BFT (Byzantine Fault Tolerance) Consensus__: A PBFT-inspired consensus algorithm with transaction finality, by AMIS. See [Istanbul BFT Consensus documentation](https://github.com/ethereum/EIPs/issues/650), the [RPC API](../istanbul-rpc-api), and this [technical web article](https://medium.com/getamis/istanbul-bft-ibft-c2758b7fe6ff) for more information + + +* __Clique POA Consensus__: a default POA consensus algorithm bundled with Go Ethereum. See [Clique POA Consensus Documentation](https://github.com/ethereum/EIPs/issues/225) and a [guide to setup clique json](https://hackernoon.com/hands-on-creating-your-own-local-private-geth-node-beginner-friendly-3d45902cc612) with [puppeth](https://blog.ethereum.org/2017/04/14/geth-1-6-puppeth-master/) diff --git a/docs/istanbul-rpc-api.md b/docs/Consensus/istanbul-rpc-api.md similarity index 99% rename from docs/istanbul-rpc-api.md rename to docs/Consensus/istanbul-rpc-api.md index baa8302db..8e8168f65 100644 --- a/docs/istanbul-rpc-api.md +++ b/docs/Consensus/istanbul-rpc-api.md @@ -2,7 +2,7 @@ This is an up to date copy of original wiki entry located here https://github.com/getamis/go-ethereum/wiki/RPC-API -# Getting Started +## Getting Started 1. Run Istanbul geth with `--rpcapi "istanbul"` 2. `geth attach` diff --git a/docs/raft.md b/docs/Consensus/raft.md similarity index 89% rename from docs/raft.md rename to docs/Consensus/raft.md index d114db5ed..916124260 100644 --- a/docs/raft.md +++ b/docs/Consensus/raft.md @@ -2,7 +2,7 @@ ## Introduction -This directory holds an implementation of a [Raft](https://raft.github.io)-based consensus mechanism (using [etcd](https://github.com/coreos/etcd)'s [Raft implementation](https://github.com/coreos/etcd/tree/master/raft)) as an alternative to Ethereum's default proof-of-work. This is useful for closed-membership/consortium settings where byzantine fault tolerance is not a requirement, and there is a desire for faster blocktimes (on the order of milliseconds instead of seconds) and transaction finality (the absence of forking.) Also, compared with QuorumChain, this consensus mechanism does not "unnecessarily" create empty blocks, and effectively creates blocks "on-demand." +The link attached holds an implementation of a [Raft](https://raft.github.io)-based consensus mechanism (using [etcd](https://github.com/coreos/etcd)'s [Raft implementation](https://github.com/coreos/etcd/tree/master/raft)) as an alternative to Ethereum's default proof-of-work. This is useful for closed-membership/consortium settings where byzantine fault tolerance is not a requirement, and there is a desire for faster blocktimes (on the order of milliseconds instead of seconds) and transaction finality (the absence of forking.) Also, compared with QuorumChain, this consensus mechanism does not "unnecessarily" create empty blocks, and effectively creates blocks "on-demand." When the `geth` binary is passed the `--raft` flag, the node will operate in "raft mode." @@ -31,7 +31,7 @@ We use the existing Ethereum p2p transport layer to communicate transactions bet When the minter creates a block, unlike in vanilla Ethereum where the block is written to the database and immediately considered the new head of the chain, we only insert the block or set it to be the new head of the chain once the block has flown through Raft. All nodes will extend the chain together in lock-step as they "apply" their Raft log. -From the point of view of Ethereum, Raft is integrated via an implementation of the [`Service`](https://godoc.org/github.com/jpmorganchase/quorum/node#Service) interface in [node/service.go](https://github.com/jpmorganchase/quorum/blob/master/node/service.go): "an individual protocol that can be registered into a node". Other examples of services are [`Ethereum`](https://godoc.org/github.com/jpmorganchase/quorum/eth#Ethereum), [`ReleaseService`](https://godoc.org/github.com/jpmorganchase/quorum/contracts/release#ReleaseService), and [`Whisper`](https://godoc.org/github.com/jpmorganchase/quorum/whisper/whisperv5#Whisper). +From the point of view of Ethereum, Raft is integrated via an implementation of the [`Service`](https://godoc.org/github.com/jpmorganchase/quorum/node#Service) interface in [`node/service.go`](https://github.com/jpmorganchase/quorum/blob/master/node/service.go): "an individual protocol that can be registered into a node". Other examples of services are [`Ethereum`](https://godoc.org/github.com/jpmorganchase/quorum/eth#Ethereum) and [`Whisper`](https://godoc.org/github.com/jpmorganchase/quorum/whisper/whisperv5#Whisper). ## The lifecycle of a transaction @@ -52,7 +52,7 @@ Let's follow the lifecycle of a typical transaction: 6. _At this point, Raft comes to consensus and appends the log entry containing our block to the Raft log. (The way this happens at the Raft layer is that the leader sends an `AppendEntries` to all followers, and they acknowledge receipt of the message. Once the leader has received a quorum of such acknowledgements, it notifies each node that this new entry has been committed permanently to the log)._ -7. Having crossed the network through Raft, the block reaches the `eventLoop` (which processes new Raft log entries.) It has arrived from the leader through `pm.transport`, an instance of [`rafthttp.Transport`](https://godoc.org/github.com/coreos/etcd/rafthttp#Transport). +7. Having crossed the network through Raft, the block reaches the `eventLoop` (which processes new Raft log entries.) It has arrived from the leader through `pm.transport`, an instance of `rafthttp.Transport`. 8. The block is now handled by `applyNewChainHead`. This method checks whether the block extends the chain (i.e. it's parent is the current head of the chain; see below). If it does not extend the chain, it is simply ignored as a no-op. If it does extend chain, the block is validated and then written as the new head of the chain by [`InsertChain`](https://godoc.org/github.com/jpmorganchase/quorum/core#BlockChain.InsertChain). @@ -158,11 +158,11 @@ To add a node to the cluster, attach to a JS console and issue `raft.addPeer(eno ## FAQ -### Could you have a single- or two-node cluster? More generally, could you have an even number of nodes? +**Could you have a single- or two-node cluster? More generally, could you have an even number of nodes ?** A cluster can tolerate failures that leave a quorum (majority) available. So a cluster of two nodes can't tolerate any failures, three nodes can tolerate one, and five nodes can tolerate two. Typically Raft clusters have an odd number of nodes, since an even number provides no failure tolerance benefit. -### What happens if you don't assume minter and leader are the same node? +**What happens if you don't assume minter and leader are the same node?** There's no hard reason they couldn't be different. We just co-locate the minter and leader as an optimization. @@ -171,14 +171,14 @@ There's no hard reason they couldn't be different. We just co-locate the minter Additionally there could even be multiple minters running at the same time, but this would produce contention for which blocks actually extend the chain, reducing the productivity of the cluster (see "races" above). -### I thought there were no forks in a Raft-based blockchain. What's the deal with "speculative minting"? +**I thought there were no forks in a Raft-based blockchain. What's the deal with "speculative minting"?** "Speculative chains" are not forks in the blockchain. They represent a series ("chain") of blocks that have been sent through Raft, after which each of the blocks may or may not actually end up being included in *the blockchain*. -### Can transactions be reversed? Since raft log entries can be disregarded as "no-ops", does this imply transaction reversal? +**Can transactions be reversed? Since raft log entries can be disregarded as "no-ops", does this imply transaction reversal?** No. When a Raft log entry containing a new block is disregarded as a "no-op", its transactions will remain in the transaction pool, and so they will be included in a future block in the chain. -### What's the deal with the block timestamp being stored in nanoseconds (instead of seconds, like other consensus mechanisms)? +**What's the deal with the block timestamp being stored in nanoseconds (instead of seconds, like other consensus mechanisms)?** With raft-based consensus we can produce far more than one block per second, which vanilla Ethereum implicitly disallows (as the default timestamp resolution is in seconds and every block must have a timestamp greater than its parent). For Raft, we store the timestamp in nanoseconds and ensure it is incremented by at least 1 nanosecond per block. diff --git a/docs/Getting Started/7Nodes.md b/docs/Getting Started/7Nodes.md new file mode 100644 index 000000000..72e09fcc8 --- /dev/null +++ b/docs/Getting Started/7Nodes.md @@ -0,0 +1,220 @@ +# 7nodes +## Demonstrating Privacy +The 7nodes example comes with some simple contracts to demonstrate the privacy features of Quorum. In this demo we will: + +* Send a private transaction between nodes 1 and 7 +* Show that only nodes 1 and 7 are able to view the initial state of the contract +* Have Node 1 update the state of the contract and, once the block containing the updated transaction is validated by the network, again verify that only nodes 1 and 7 are able to see the updated state of the contract + +!!! tip + [Constellation](../../Privacy/Constellation/Constellation) or [Tessera](../../Privacy/Tessera/Tessera) is used to enable the privacy features of Quorum. To start a Quorum node without its associated privacy transaction manager, set `PRIVATE_CONFIG=ignore` when starting the node. + +### Sending a private transaction + +First start running the 7nodes example by following the instructions in the [quorum-examples](../Quorum-Examples#getting-started), then send an example private contract from Node 1 to Node 7 (this is denoted by the public key passed via `privateFor: ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]` in `private-contract.js`): +``` bash +./runscript.sh private-contract.js +``` +Make note of the `TransactionHash` printed to the terminal. + +### Inspecting the Quorum nodes + +We can inspect any of the Quorum nodes by using `geth attach` to open the Geth JavaScript console. For this demo, we will be inspecting Node 1, Node 7 and Node 4. + +It is recommended to use separate terminal windows for each node we are inspecting. In each terminal, ensure you are in the `path/to/7nodes` directory, then: + +- If you aren't already running the 7nodes example, in terminal 1 run `./{consensus}-init.sh` followed by `./{consensus}-start.sh` +- In terminal 1 run `geth attach ipc:qdata/dd1/geth.ipc` to attach to node 1 +- In terminal 2 run `geth attach ipc:qdata/dd4/geth.ipc` to attach to node 4 +- In terminal 3 run `geth attach ipc:qdata/dd7/geth.ipc` to attach to node 7 + +To look at the private transaction that was just sent, run the following command in one of the terminals: +``` sh +eth.getTransaction("0xe28912c5694a1b8c4944b2252d5af21724e9f9095daab47bac37b1db0340e0bf") +``` +where you should replace this hash with the TransactionHash that was previously printed to the terminal. This will print something of the form: +``` sh +{ + blockHash: "0x4d6eb0d0f971b5e0394a49e36ba660c69e62a588323a873bb38610f7b9690b34", + blockNumber: 1, + from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d", + gas: 4700000, + gasPrice: 0, + hash: "0xe28912c5694a1b8c4944b2252d5af21724e9f9095daab47bac37b1db0340e0bf", + input: "0x58c0c680ee0b55673e3127eb26e5e537c973cd97c70ec224ccca586cc4d31ae042d2c55704b881d26ca013f15ade30df2dd196da44368b4a7abfec4a2022ec6f", + nonce: 0, + r: "0x4952fd6cd1350c283e9abea95a2377ce24a4540abbbf46b2d7a542be6ed7cce5", + s: "0x4596f7afe2bd23135fa373399790f2d981a9bb8b06144c91f339be1c31ec5aeb", + to: null, + transactionIndex: 0, + v: "0x25", + value: 0 +} +``` + +Note the `v` field value of `"0x25"` or `"0x26"` (37 or 38 in decimal) which indicates this transaction has a private payload (input). + + +#### Checking the state of the contract +For each of the 3 nodes we'll use the Geth JavaScript console to create a variable called `address` which we will assign to the address of the contract created by Node 1. The contract address can be found in two ways: + +- In Node 1's log file: `7nodes/qdata/logs/1.log` +- By reading the `contractAddress` param after calling `eth.getTransactionReceipt(txHash)` ([Ethereum API documentation](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethgettransactionreceipt)) where `txHash` is the hash printed to the terminal after sending the transaction. + +Once you've identified the contract address, run the following command in each terminal: +``` javascript +> var address = "0x1932c48b2bf8102ba33b4a6b545c32236e342f34"; //replace with your contract address +``` + +Next we'll use ```eth.contract``` to define a contract class with the simpleStorage ABI definition in each terminal: +``` javascript +> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"type":"constructor"}]; +> var private = eth.contract(abi).at(address) +``` + +The function calls are now available on the contract instance and you can call those methods on the contract. Let's start by examining the initial value of the contract to make sure that only nodes 1 and 7 can see the initialized value. +- In terminal window 1 (Node 1): +``` javascript +> private.get() +42 +``` +- In terminal window 2 (Node 4): +``` javascript +> private.get() +0 +``` +- In terminal window 3 (Node 7): +``` javascript +> private.get() +42 +``` + +So we can see nodes 1 and 7 are able to read the state of the private contract and its initial value is 42. If you look in `private-contract.js` you will see that this was the value set when the contract was created. Node 4 is unable to read the state. + +### Updating the state of the contract + +Next we'll have Node 1 set the state to the value `4` and verify only nodes 1 and 7 are able to view the new state. + +In terminal window 1 (Node 1): +``` javascript +> private.set(4,{from:eth.coinbase,privateFor:["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]}); +"0xacf293b491cccd1b99d0cfb08464a68791cc7b5bc14a9b6e4ff44b46889a8f70" +``` +You can check the log files in `7nodes/qdata/logs/` to see each node validating the block with this new private transaction. Once the block containing the transaction has been validated we can once again check the state from each node 1, 4, and 7. +- In terminal window 1 (Node 1): +``` javascript +> private.get() +4 +``` +- In terminal window 2 (Node 4): +``` javascript +> private.get() +0 +``` +- In terminal window 3 (Node 7): +``` javascript +> private.get() +4 +``` +And there you have it. All 7 nodes are validating the same blockchain of transactions, the private transactions carrying only a 512 bit hash, and only the parties to private transactions are able to view and update the state of private contracts. + +## Permissions + +Node Permissioning is a feature in Quorum that allows only a pre-defined set of nodes (as identified by their remotekey/enodes) to connect to the permissioned network. + +In this demo we will: +- Set up a network with a combination of permissioned and non-permissioned nodes in the cluster +- Look at the details of the `permissioned-nodes.json` file +- Demonstrate that only the nodes that are specified in `permissioned-nodes.json` can connect to the network + +### Verify only permissioned nodes are connected to the network. + +Attach to the individual nodes via `geth attach path/to/geth.ipc` and use `admin.peers` to check the connected nodes: + +``` sh +❯ geth attach qdata/dd1/geth.ipc +Welcome to the Geth JavaScript console! + +instance: Geth/v1.7.2-stable/darwin-amd64/go1.9.2 +coinbase: 0xed9d02e382b34818e88b88a309c7fe71e65f419d +at block: 1 (Mon, 29 Oct 47909665359 22:09:51 EST) + datadir: /Users/joel/jpm/quorum-examples/examples/7nodes/qdata/dd1 + modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 raft:1.0 rpc:1.0 txpool:1.0 web3:1.0 + +> admin.peers +[{ + caps: ["eth/63"], + id: "0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416", + name: "Geth/v1.7.2-stable/darwin-amd64/go1.9.2", + network: { + localAddress: "127.0.0.1:65188", + remoteAddress: "127.0.0.1:21001" + }, + protocols: { + eth: { + difficulty: 0, + head: "0xc23b4ebccc79e2636d66939924d46e618269ca1beac5cf1ec83cc862b88b1b71", + version: 63 + } + } +}, +... +] +``` + +You can also inspect the log files under `qdata/logs/*.log` for further diagnostics messages around incoming / outgoing connection requests. `grep` for `ALLOWED-BY` or `DENIED-BY`. Be sure to enable verbosity for p2p module. + +### Permissioning configuration + +Permissioning is granted based on the remote key of the geth node. The remote keys are specified in the `permissioned-nodes.json` and is placed under individual node's ``. + +The below sample `permissioned-nodes.json` provides a list of nodes permissioned to join the network (node ids truncated for clarity): + +``` json +[ + "enode://8475a01f22a1f48116dc1f0d22ecaaaf77e@127.0.0.1:30301", + "enode://b5660501f496e60e59ded734a889c97b7da@127.0.0.1:30302", + "enode://54bd7ff4bd971fb80493cf4706455395917@127.0.0.1:30303" +] +``` + +### Enabling/Disabling permissions + +An individual node can enable/disable permissioning by passing the `-permissioned` command line flag. If enabled, then only the nodes that are in the `/permissioned-nodes.json` can connect to it. Further, these are the only nodes that this node can make outbound connections to as well. + +``` +MISCELLANEOUS OPTIONS: +--permissioned If enabled, the node will allow only a defined list of nodes to connect +``` + +## Next steps +Additional samples can be found in `quorum-examples/examples/7nodes/samples` for you to use and edit. You can also create your own contracts to help you understand how the nodes in a Quorum network work together. + +## Reducing the number of nodes +It is easy to reduce the number of nodes used in the example. You may want to do this for memory usage reasons or just to experiment with a different network configuration. + +To run the example with 5 nodes instead of 7, the following changes need to be made: +1. In __`raft-start.sh`__: + + Comment out the following lines used to start Quorum nodes 6 & 7 + ```sh + # PRIVATE_CONFIG=qdata/c6/tm.ipc nohup geth --datadir qdata/dd6 $ARGS --raftport 50406 --rpcport 22005 --port 21005 --unlock 0 --password passwords.txt 2>>qdata/logs/6.log & + # PRIVATE_CONFIG=qdata/c7/tm.ipc nohup geth --datadir qdata/dd7 $ARGS --raftport 50407 --rpcport 22006 --port 21006 --unlock 0 --password passwords.txt 2>>qdata/logs/7.log & + ``` + +1. In __`constellation-start.sh`__ or __`tessera-start.sh`__ (depending on which privacy manager you are using): + + Change the 2 instances of `for i in {1..7}` to `for i in {1..5}` + +After making these changes, the `raft-init.sh` and `raft-start.sh` scripts can be run as normal. + +`private-contract.js` will also need to be updated as this is set up to send a transaction from node 1 to node 7. To update the private contract to instead send to node 5, the following steps need to be followed: + +1. Copy node 5's public key from `./keys/tm5.pub` + +2. Replace the existing `privateFor` in `private-contract.js` with the key copied from `tm5.pub` key, e.g.: + ``` javascript + var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["R56gy4dn24YOjwyesTczYa8m5xhP6hF2uTMCju/1xkY="]}, function(e, contract) {...} + ``` + +After saving this change, the `./runscript.sh private-contract.js` command can be run as usual to submit the private contract. You can then follow steps described above to verify that node 5 can see the transaction payload and that nodes 2-4 are unable to see the payload. diff --git a/docs/Getting Started/Getting-Started-From-Scratch.md b/docs/Getting Started/Getting-Started-From-Scratch.md new file mode 100644 index 000000000..6e4af5db0 --- /dev/null +++ b/docs/Getting Started/Getting-Started-From-Scratch.md @@ -0,0 +1,66 @@ +# Getting started from scratch +## Quorum with Raft consensus +1. Build Quorum as described in the [getting set up](../Setup%20Overview%20%26%20Quickstart) section. Ensure that PATH contains geth and bootnode +2. Create a working directory which will be the base for the new node(s) and change into it +3. Generate one or more accounts for this node using `geth --datadir new-node-1 account new` and take down the account address. A funded account may be required depending what you are trying to accomplish +4. Create a `genesis.json` file see example [here](../genesis). The `alloc` field should be pre-populated with the account you generated at previous step +5. Generate node key `bootnode --genkey=nodekey` and copy it into datadir +6. Execute `bootnode --nodekey=new-node-1/nodekey --writeaddress` and take note of the displayed output. This is the enode id of the new node +7. Create a file called `static-nodes.json` and edit it to match this [example](../permissioned-nodes). Your file should contain a single line for your node with your enode's id and the ports you are going to use for devp2p and raft. Ensure that this file is in your nodes data directory +8. Initialize new node with `geth --datadir new-node-1 init genesis.json` +9. Start your node and send into background with `PRIVATE_CONFIG=ignore nohup geth --datadir new-node-1 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50000 --rpc --rpcaddr 0.0.0.0 --rpcport 22000 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21000 2>>node.log &` + +Your node is now operational and you may attach to it with `geth attach new-node-1/geth.ipc`. This configuration starts Quorum without privacy support as could be evidenced in prefix `PRIVATE_CONFIG=ignore`, please see below sections on [how to enable privacy with privacy transaction managers](../Getting-Started-From-Scratch#adding-privacy-transaction-manager). + +### Adding additional node +1. Complete steps 1, 2, 5, and 6 from the previous guide +2. Retrieve current chains `genesis.json` and `static-nodes.json`. `static-nodes.json` should be placed into new nodes data dir +3. Initialize new node with `geth --datadir new-node-2 init genesis.json` +4. Edit `static-nodes.json` and add new entry for the new node you are configuring (should be last) +5. Start your node and send into background with `PRIVATE_CONFIG=ignore nohup geth --datadir new-node-2 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50005 --rpc --rpcaddr 0.0.0.0 --rpcport 22005 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21005 2>>node.log &` +6. Connect to an already running node of the chain and execute `raft.addPeer('enode://new-nodes-enode-address-from-step-6-of-the-above@127.0.0.1:21005?discport=0&raftport=50005')` +7. Optional: share new `static-nodes.json` with all other chain participants + +Your additional node is now operational and is part of the same chain as the previously set up node. + + + +## Quorum with Istanbul BFT consensus +1. Build Quorum as described in the [getting set up](../Setup%20Overview%20%26%20Quickstart) section. Ensure that PATH contains geth and bootnode +2. Install [istanbul-tools](https://github.com/jpmorganchase/istanbul-tools) and place `istanbul` binary into PATH +3. Create a working directory for each of the X number of initial validator nodes +4. Change into the lead (whichever one you consider first) node's working directory and generate the setup files for X initial validator nodes by executing `istanbul setup --num X --nodes --quorum --save --verbose` **only execute this instruction once, i.e. not X times**. This command will generate several items of interest: `static-nodes.json`, `genesis.json`, and nodekeys for all the initial validator nodes which will sit in numbered directories from 0 to X-1 +5. Update `static-nodes.json` to include the intended IP and port numbers of all initial validator nodes. In `static-nodes.json`, you will see a different row for each node. For the rest of the installation guide, row Y refers to node Y and row 1 is assumed to correspond to the lead node +6. In each node's working directory, create a data directory called `data`, and inside `data` create the `geth` directory +7. Now we will generate initial accounts for any of the nodes by executing `geth --datadir data account new` in the required node's working directory. The resulting public account address printed in the terminal should be recorded. Repeat as many times as necessary. A set of funded accounts may be required depending what you are trying to accomplish +8. To add accounts to the initial block, edit the `genesis.json` file in the lead node's working directory and update the `alloc` field with the account(s) that were generated at previous step +9. Next we need to distribute the files created in part 4, which currently reside in the lead node's working directory, to all other nodes. To do so, place `genesis.json` in the working directory of all nodes, place `static-nodes.json` in the data folder of each node and place `X/nodekey` in node (X-1)'s `data/geth` directory +10. Switch into working directory of lead node and initialize it with `geth --datadir data init genesis.json`. Repeat for every working directory X created in step 3. *The resulting hash given by executing `geth init` must match for every node* +11. Start all nodes and send into background with `PRIVATE_CONFIG=ignore nohup geth --datadir data --permissioned --nodiscover --istanbul.blockperiod 5 --syncmode full --mine --minerthreads 1 --verbosity 5 --networkid 10 --rpc --rpcaddr 0.0.0.0 --rpcport YOUR_NODES_RPC_PORT_NUMBER --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul --emitcheckpoints --port YOUR_NODES_PORT_NUMBER 2>>node.log &`, remember to replace `YOUR_NODES_RPC_PORT_NUMBER` and `YOUR_NODES_PORT_NUMBER` with your node's designated port numbers. `YOUR_NODES_PORT_NUMBER` must match the port number for this node decided on in part 5 + +Your node is now operational and you may attach to it with `geth attach data/geth.ipc`. This configuration starts Quorum without privacy support as could be evidenced in prefix `PRIVATE_CONFIG=ignore`, please see below sections on [[how to enable privacy with privacy transaction managers|From-Scratch#adding-privacy-transaction-manager]]. + +Please note that istanbul-tools may be used to generate X number of nodes, more information is available in the [docs](https://github.com/jpmorganchase/istanbul-tools). + + +## Adding privacy transaction manager +### Tessera +1. Build Quorum and install [Tessera](https://github.com/jpmorganchase/tessera/releases) as described in the [getting set up](../Setup%20Overview%20%26%20Quickstart) section. Ensure that PATH contains geth and bootnode. Be aware of the location of the `tessera.jar` release file +2. Generate new keys using `java -jar /path-to-tessera/tessera.jar -keygen -filename new-node-1` +3. Create new configuration file referencing samples [here](../../Privacy/Tessera/Configuration/Sample%20Configuration) with newly generated keys referenced. Note the name of the file or name it `config.json` as done in this example +4. Start your tessera node and send it into background with `java -jar /path-to-tessera/tessera.jar -configfile config.json >> tessera.log 2>&1 &` +5. Start your node and send it into background with `PRIVATE_CONFIG=tm.ipc nohup geth --datadir new-node-1 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50000 --rpc --rpcaddr 0.0.0.0 --rpcport 22000 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21000 2>>node.log &` + +Your node is now operational and you may attach to it with `geth attach new-node-1/geth.ipc`. Tessera IPC bridge will be over a file name defined in your `config.json`, usually named `tm.ipc` as evidenced in prefix `PRIVATE_CONFIG=tm.ipc`. Your node is now able to send and receive private transactions, advertised public node key will be in the `new-node-1.pub` file. Tessera offers a lot of configuration flexibility, please refer [Configuration](../../Privacy/Tessera/Configuration/Configuration%20Overview) section under Tessera for complete and up to date configuration options. + +### Constellation +1. Build Quorum and install [Constellation](https://github.com/jpmorganchase/constellation/releases) as described in the [getting set up](../Setup%20Overview%20%26%20Quickstart) section. Ensure that PATH contains geth, bootnode, and constellation-node binaries +2. Generate new keys with `constellation-node --generatekeys=new-node-1` +3. Start your constellation node and send it into background with `constellation-node --url=https://127.0.0.1:9001/ --port=9001 --workdir=. --socket=tm.ipc --publickeys=new-node-1.pub --privatekeys=new-node-1.key --othernodes=https://127.0.0.1:9001/ >> constellation.log 2>&1 &` +4. Start your node and send it into background with `PRIVATE_CONFIG=tm.ipc nohup geth --datadir new-node-1 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50000 --rpc --rpcaddr 0.0.0.0 --rpcport 22000 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21000 2>>node.log &` + +Your node is now operational and you may attach to it with `geth attach new-node-1/geth.ipc`. Constellation IPC bridge will be over a file name defined in your configuration: in above step #3 see option `--socket=file-name.ipc`. Your node is now able to send and receive private transactions, advertised public node key will be in the `new-node-1.pub` file. + + +## Enabling permissioned configuration +Quorum ships with a permissions system based on a custom whitelist. Detailed documentation is available in [Network Permissioning](../../Security/Security%20%26%20Permissioning). diff --git a/docs/Getting Started/Quorum-Examples.md b/docs/Getting Started/Quorum-Examples.md new file mode 100644 index 000000000..e38681876 --- /dev/null +++ b/docs/Getting Started/Quorum-Examples.md @@ -0,0 +1,179 @@ +# Quorum Examples + +This repository contains setup examples for Quorum. + +Current examples include: + +* [7nodes](../7Nodes/): Starts up a fully-functioning Quorum environment consisting of 7 independent nodes. From this example one can test consensus, privacy, and all the expected functionality of an Ethereum platform. +* [5nodesRTGS](https://github.com/bacen/quorum-examples/tree/master/examples/5nodesRTGS): [__Note__: This links to an external repo which you will need to clone, thanks to @rsarres for this contribution!] Starts up a set of 5 nodes that simulates a Real-time Gross Setlement environment with 3 banks, one regulator (typically a central bank) and an observer that cannot access the private data. + +The easiest way to get started with running the examples is to use the vagrant environment (see below). + +**Important note**: Any account/encryption keys contained in this repository are for +demonstration and testing purposes only. Before running a real environment, you should +generate new ones using Geth's `account` tool and the `--generate-keys` option for Constellation (or `-keygen` option for Tessera). + +## Getting Started +The 7nodes example can be run in three ways: + +1. By running a preconfigured Vagrant environment which comes complete with Quorum, Constellation, Tessera and the 7nodes example (__works on any machine__). +1. By running [`docker-compose`](https://docs.docker.com/compose/) against a preconfigured `compose` file ([example](https://github.com/jpmorganchase/quorum-examples/blob/master/docker-compose.yml) from the `quorum-examples` repo) which starts 7nodes example (tested on Windows 10, macOS Mojave & Ubuntu 18.04). +1. By downloading and locally running Quorum, Tessera and the examples (__requires an Ubuntu-based/macOS machine; note that Constellation does not support running locally__) + +### Setting up Vagrant +1. Install [VirtualBox](https://www.virtualbox.org/wiki/Downloads) +2. Install [Vagrant](https://www.vagrantup.com/downloads.html) +3. Download and start the Vagrant instance (note: running `vagrant up` takes approx 5 mins): + + ```sh + git clone https://github.com/jpmorganchase/quorum-examples + cd quorum-examples + vagrant up + vagrant ssh + ``` + +4. To shutdown the Vagrant instance, run `vagrant suspend`. To delete it, run + `vagrant destroy`. To start from scratch, run `vagrant up` after destroying the + instance. + +#### Troubleshooting Vagrant +* If you are behind a proxy server, please see https://github.com/jpmorganchase/quorum/issues/23. +* If you are using macOS and get an error saying that the ubuntu/xenial64 image doesn't +exist, please run `sudo rm -r /opt/vagrant/embedded/bin/curl`. This is usually due to +issues with the version of curl bundled with Vagrant. +* If you receive the error `default: cp: cannot open '/path/to/geth.ipc' for reading: Operation not supported` after running `vagrant up`, run `./raft-init.sh` within the 7nodes directory on your local machine. This will remove temporary files created after running 7nodes locally and will enable `vagrant up` to execute correctly. + +#### Troubleshooting Vagrant: Memory usage +* The Vagrant instance is allocated 6 GB of memory. This is defined in the `Vagrantfile`, `v.memory = 6144`. This has been deemed a suitable value to allow the VM and examples to run as expected. The memory allocation can be changed by updating this value and running `vagrant reload` to apply the change. + +* If the machine you are using has less than 8 GB memory you will likely encounter system issues such as slow down and unresponsiveness when starting the Vagrant instance as your machine will not have the capacity to run the VM. There are several steps that can be taken to overcome this: + 1. Shutdown any running processes that are not required + 1. If running the [7nodes example](../7Nodes), reduce the number of nodes started up. See the [7nodes: Reducing the number of nodes](../7Nodes#reducing-the-number-of-nodes) for info on how to do this. + 1. Set up and run the examples locally. Running locally reduces the load on your memory compared to running in Vagrant. + +### Setting up Docker + +1. Install Docker (https://www.docker.com/get-started) + * If your Docker distribution does not contain `docker-compose`, follow [this](https://docs.docker.com/compose/install/) to install Docker Compose + * Make sure your Docker daemon has at least 4G memory +1. Download and run `docker-compose` + ```sh + git clone https://github.com/jpmorganchase/quorum-examples + cd quorum-examples + docker-compose up -d + ``` +1. By default, Quorum Network is created using Tessera transaction manager and Istanbul BFT consensus. If you wish to change consensus configuration to Raft, set the environment variable `QUORUM_CONSENSUS=raft` before running `docker-compose` + ```sh + QUORUM_CONSENSUS=raft docker-compose up -d + ``` +1. Run `docker ps` to verify that all quorum-examples containers (7 nodes and 7 tx managers) are **healthy** +1. __Note__: to run the 7nodes demo, use the following snippet to open `geth` Javascript console to a desired node (using container name from `docker ps`) and send a private transaction + ```sh + $ docker exec -it quorum-examples_node1_1 geth attach /qdata/dd/geth.ipc + Welcome to the Geth JavaScript console! + + instance: Geth/node1-istanbul/v1.7.2-stable/linux-amd64/go1.9.7 + coinbase: 0xd8dba507e85f116b1f7e231ca8525fc9008a6966 + at block: 70 (Thu, 18 Oct 2018 14:49:47 UTC) + datadir: /qdata/dd + modules: admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 + + > loadScript('/examples/private-contract.js') + ``` +1. Shutdown Quorum Network + ```sh + docker-compose down + ``` + +#### Troubleshooting Docker + +1. Docker is frozen + * Check if your Docker daemon is allocated enough memory (minimum 4G) +1. Tessera is crashed due to missing file/directory + * This is due to the location of `quorum-examples` folder is not shared + * Please refer to Docker documentation for more details: + * [Docker Desktop for Windows](https://docs.docker.com/docker-for-windows/troubleshoot/#shared-drives) + * [Docker Desktop for Mac](https://docs.docker.com/docker-for-mac/#file-sharing) + * [Docker Machine](https://docs.docker.com/machine/overview/): this depends on what Docker machine provider is used. Please refer to its documentation on how to configure shared folders/drives + +### Setting up locally + +!!! info + This is only possible with Tessera. Constellation is not supported when running the examples locally. To use Constellation, the examples must be run in Vagrant. + +1. Install [Golang](https://golang.org/dl/) +2. Download and build [Quorum](https://github.com/jpmorganchase/quorum/): + + ```sh + git clone https://github.com/jpmorganchase/quorum + cd quorum + make + GETHDIR=`pwd`; export PATH=$GETHDIR/build/bin:$PATH + cd .. + ``` + +3. Download and build Tessera (see [README](https://github.com/jpmorganchase/tessera) for build options) + + ```bash + git clone https://github.com/jpmorganchase/tessera.git + cd tessera + mvn install + ``` + +4. Download quorum-examples + ```sh + git clone https://github.com/jpmorganchase/quorum-examples + ``` + +### Running the 7nodes example +Shell scripts are included in the examples to make it simple to configure the network and start submitting transactions. + +All logs and temporary data are written to the `qdata` folder. + +#### Using Raft consensus + +1. Navigate to the 7nodes example, configure the Quorum nodes and initialize accounts & keystores: + ```sh + cd path/to/7nodes + ./raft-init.sh + ``` +2. Start the Quorum and privacy manager nodes (Constellation or Tessera): + - If running in Vagrant: + ```sh + ./raft-start.sh + ``` + By default, Constellation will be used as the privacy manager. To use Tessera run the following: + ``` + ./raft-start.sh tessera + ``` + By default, `raft-start.sh` will look in `/home/vagrant/tessera/tessera-app/target/tessera-app-{version}-app.jar` for the Tessera jar. + + - If running locally with Tessera: + ``` + ./raft-start.sh tessera --tesseraOptions "--tesseraJar /path/to/tessera-app.jar" + ``` + + The Tessera jar location can also be specified by setting the environment variable `TESSERA_JAR`. + +3. You are now ready to start sending private/public transactions between the nodes + +#### Using Istanbul BFT consensus +To run the example using __Istanbul BFT__ consensus use the corresponding commands: +```sh +istanbul-init.sh +istanbul-start.sh +istanbul-start.sh tessera +stop.sh +``` + +#### Using Clique POA consensus +To run the example using __Clique POA__ consensus use the corresponding commands: +```sh +clique-init.sh +clique-start.sh +clique-start.sh tessera +stop.sh +``` + +### Next steps: Sending transactions +Some simple transaction contracts are included in quorum-examples to demonstrate the privacy features of Quorum. To learn how to use them see the [7nodes](../7Nodes). diff --git a/docs/Getting Started/Setup Overview & Quickstart.md b/docs/Getting Started/Setup Overview & Quickstart.md new file mode 100644 index 000000000..c640ac578 --- /dev/null +++ b/docs/Getting Started/Setup Overview & Quickstart.md @@ -0,0 +1,33 @@ +# Setup Overview & Quickstart + +Using Quorum requires that a Quorum Node and a Constellation/Tessera Node are installed, configured and +running (see build/installation instructions for both below). An overview of the steps to follow to manually set up Quorum, including key generation, genesis block & Constellation/Tessera configuration will be available soon, but for now the best way to get started is to use the Vagrant environment that has been made available for running the [Quorum Examples](../Quorum-Examples). The Vagrant environment automatically sets up a test Quorum network that is ready for development use within minutes and is the recommended approach if you are looking to get started with Quorum. If you don't want to use the Quorum Examples approach and instead would like to manually set up Quorum then please see below (Note: this documentation is Work In Progress) + +## Building Quorum Node From Source + +Clone the repository and build the source: + +``` +git clone https://github.com/jpmorganchase/quorum.git +cd quorum +make all +``` + +Binaries are placed in `$REPO_ROOT/build/bin`. Put that folder in your PATH to make `geth` and `bootnode` easily invokable, or copy those binaries to a folder already in PATH, e.g. `/usr/local/bin`. + +An easy way to supplement PATH is to add `PATH=$PATH:/path/to/repository/build/bin` to your `~/.bashrc` or `~/.bash_aliases` file. + +Run the tests: + +``` +make test +``` + +## Installing Constellation +Grab a package for your platform [here](https://github.com/jpmorganchase/constellation/releases), and place the extracted binaries somewhere in PATH, e.g. /usr/local/bin. + +## Installing Tessera +Follow the installation instructions on the [Tessera project page](https://github.com/jpmorganchase/tessera). + +## Getting Started from Scratch +Follow the instructions given [here](../Getting-Started-From-Scratch). diff --git a/docs/Getting Started/genesis.md b/docs/Getting Started/genesis.md new file mode 100644 index 000000000..494100ac3 --- /dev/null +++ b/docs/Getting Started/genesis.md @@ -0,0 +1,39 @@ +``` json +{ + "alloc": { + "0xed9d02e382b34818e88b88a309c7fe71e65f419d": { + "balance": "1000000000000000000000000000" + }, + "0xca843569e3427144cead5e4d5999a3d0ccf92b8e": { + "balance": "1000000000000000000000000000" + }, + "0x0fbdc686b912d7722dc86510934589e0aaf3b55a": { + "balance": "1000000000000000000000000000" + }, + "0x9186eb3d20cbd1f5f992a950d808c4495153abd5": { + "balance": "1000000000000000000000000000" + }, + "0x0638e1574728b6d862dd5d3a3e0942c3be47d996": { + "balance": "1000000000000000000000000000" + } + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "config": { + "homesteadBlock": 0, + "byzantiumBlock": 0, + "chainId": 10, + "eip150Block": 0, + "eip155Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip158Block": 0, + "isQuorum": true + }, + "difficulty": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0xE0000000", + "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", + "nonce": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" +} +``` diff --git a/docs/Getting Started/permissioned-nodes.md b/docs/Getting Started/permissioned-nodes.md new file mode 100644 index 000000000..7f607d1ef --- /dev/null +++ b/docs/Getting Started/permissioned-nodes.md @@ -0,0 +1,11 @@ +``` +[ + "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@127.0.0.1:21000?discport=0&raftport=50401", + "enode://0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416@127.0.0.1:21001?discport=0&raftport=50402", + "enode://579f786d4e2830bbcc02815a27e8a9bacccc9605df4dc6f20bcc1a6eb391e7225fff7cb83e5b4ecd1f3a94d8b733803f2f66b7e871961e7b029e22c155c3a778@127.0.0.1:21002?discport=0&raftport=50403", + "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404", + "enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", + "enode://eacaa74c4b0e7a9e12d2fe5fee6595eda841d6d992c35dbbcc50fcee4aa86dfbbdeff7dc7e72c2305d5a62257f82737a8cffc80474c15c611c037f52db1a3a7b@127.0.0.1:21005?discport=0&raftport=50406", + "enode://239c1f044a2b03b6c4713109af036b775c5418fe4ca63b04b1ce00124af00ddab7cc088fc46020cdc783b6207efe624551be4c06a994993d8d70f684688fb7cf@127.0.0.1:21006?discport=0&raftport=50407" +] +``` diff --git a/docs/Getting Started/running.md b/docs/Getting Started/running.md new file mode 100644 index 000000000..fcd9feeda --- /dev/null +++ b/docs/Getting Started/running.md @@ -0,0 +1,215 @@ +# Running Quorum + +## Developing Smart Contracts +Quorum uses standard [Solidity](https://solidity.readthedocs.io/en/develop/) for writing Smart Contracts, and generally, these can be designed as you would design Smart Contracts for Ethereum. Smart Contracts can either be public (i.e. visible and executable by all participants on a given Quorum network) or private to one or more network participants. Note, however, that Quorum does not introduce new contract Types. Instead, similar to [Transactions](../../Transaction%20Processing/Transaction%20Processing), the concept of public and private contracts is notional only. + +### Creating Public Transactions/Contracts + +Sending a standard Ethereum-style transaction to a given network will make it viewable and executable by all participants on the network. As with Ethereum, leave the `to` field empty for a contract-creation transaction. + +Example JSON RPC API call to send a public transaction: + +``` json +{ + "jsonrpc":"2.0", + "method":"eth_sendTransaction", + "params":[ + { + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", // 30400 + "gasPrice": "0x9184e72a000", // 10000000000000 + "value": "0x9184e72a", // 2441406250 + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + } + ], + "id":1 +} +``` + +See the [Quorum API](../../api) page for details on the `sendTransaction` call, which includes some modifications to the standard Ethereum call. + +!!! info + See the Contract Design Considerations sections below for important points on creating Quorum contracts + +### Creating Private Transactions/Contracts +In order to make a transaction/smart contract private and therefore only viewable and executable by a subset of the network, send a standard Ethereum Transaction but include the Quorum-specific `privateFor` parameter. `privateFor` is used to provide the list of participants for the transaction/contract. Each participant is identified by a Privacy Manager public key. + +Example JSON RPC API call to send a private transaction: + +``` json +{ + "jsonrpc":"2.0", + "method":"eth_sendTransaction", + "params":[ + { + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", // 30400 + "gasPrice": "0x9184e72a000", // 10000000000000 + "value": "0x9184e72a", // 2441406250 + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", + "privateFor": ["$PUBKEY1, $PUBKEY2"] + } + ], + "id":1 +} +``` + +See the [Quorum API](../../api) page for details on the `sendTransaction` call, which includes some modifications to the standard Ethereum call. + +!!! info + See the Contract Design Considerations sections below for important points on creating Quorum contracts + +### Quorum Contract Design Considerations + +1. *Private contracts cannot update public contracts.* This is because not all participants will be able to execute a private contract, and so if that contract can update a public contract, then each participant will end up with a different state for the public contract. +2. *Once a contract has been made public, it can't later be made private.* If you do need to make a public contract private, it would need to be deleted from the blockchain and a new private contract created. + +## Setting up a multi-node network + +The [quorum-examples 7nodes](../Quorum-Examples) source files contain several scripts demonstrating how to set up a private test network made up of 7 nodes. + +## Permissioned Networks + +Node Permissioning is a feature of Quorum that is used to define: + +1. The nodes that a particular Quorum node is able to connect to +2. The nodes that a particular Quorum node is able to receive connections from + +Permissioning is managed at the individual node level by using the `--permissioned` command line flag when starting the node. + +If a node is started with `--permissioned` set, the node will look for a `/permissioned-nodes.json` file. This file contains the list of enodes that this node can connect to and accept connections from. In other words, if permissioning is enabled, only the nodes that are listed in the `permissioned-nodes.json` file become part of the network. + +If `--permissioned` is set, a `permissioned-nodes.json` file must be provided. If the flag is set but no nodes are present in this file, then the node will be unable to make any outward connections or accept any incoming connections. + +The format of `permissioned-nodes.json` is similar to `static-nodes.json`: + +```json +[ + "enode://enodehash1@ip1:port1", + "enode://enodehash2@ip2:port2", + "enode://enodehash3@ip3:port3" +] +``` + +For example, including the hash, a sample file might look like: + +```json +[ + "enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300" +] +``` + +In the current release, every node has its own copy of `permissioned-nodes.json`. In a future release, the permissioned nodes list will be moved to a smart contract, thereby keeping the list on-chain and requiring just one global list of nodes that connect to the network. + +### Initialize chain + +The first step is to generate the genesis block. + +The `7nodes` directory in the `quorum-examples` repository contains several keys (using an empty password) that are used in the example genesis file: + +``` +key1 vote key 1 +key2 vote key 2 +key3 vote key 3 +key4 block maker 1 +key5 block maker 2 +``` + +Example genesis file (copy to `genesis.json`): +``` json +"config": { + "homesteadBlock": 0, + "byzantiumBlock": 0, + "chainId": 10, + "eip150Block": 0, + "eip155Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip158Block": 0, + "isQuorum": true + }, +``` + +Now we can initialize geth: + +``` +geth init genesis.json +``` + +### Setup Bootnode +Optionally you can set up a bootnode that all the other nodes will first connect to in order to find other peers in the network. You will first need to generate a bootnode key: + +1. To generate the key for the first time: + + ``` bash + bootnode -genkey tmp_file.txt // this will start a bootnode with an enode address and generate a key inside a “tmp_file.txt” file` + ``` + +2. To later restart the bootnode using the same key (and hence use the same enode url): + + ``` bash + bootnode -nodekey tmp_file.txt + ``` + + or + + ``` bash + bootnode -nodekeyhex 77bd02ffa26e3fb8f324bda24ae588066f1873d95680104de5bc2db9e7b2e510 // Key from tmp_file.txt + ``` + +### Start node + +Starting a node is as simple as `geth`. This will start the node without any of the roles and makes the node a spectator. If you have setup a bootnode then be sure to add the `--bootnodes` param to your startup command: + +`geth --bootnodes $BOOTNODE_ENODE` + +### Adding New Nodes: +Any additions to the `permissioned-nodes.json` file will be dynamically picked up by the server when subsequent incoming/outgoing requests are made. The node does not need to be restarted in order for the changes to take effect. + +### Removing existing nodes: +Removing existing connected nodes from the `permissioned-nodes.json` file will not immediately drop those existing connected nodes. However, if the connection is dropped for any reason, and a subsequent connect request is made from the dropped node ids, it will be rejected as part of that new request. + +## Quorum API +Please see the [Quorum API](../../api) page for details. + +## Network and Chain ID + +An Ethereum network is run using a Network ID and, after [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md), a Chain ID. + +Before EIP-155, the names "Network ID" and "Chain ID" were used interchangeably, but after this they have separate meanings. + +The network ID is a property of a peer, NOT of the chain the peer is managing. A network ID can be passed in via the command line by `--networkid `. It's purpose is to separate peers that are running under a different network ID. Therefore, you cannot sync with anyone who is running a node with a different network ID. However, since it is trivial to change this, it is a less secure version of Quorum's `--permissioned` flag, and it only used for simple segregation. + +The chain ID is a property of the chain managed by the node. It is used for replay protection of transactions - prior to EIP-155, a transaction run on one chain could be copied and sent to a different chain by anyone, since the transaction is already signed. + +Setting the chain ID has the effect of changing one of the parameters of a transaction, namely the `V` parameter. +As the EIP explains, the `v` parameter is set to `2*ChainID + 35/36`. For the Ethereum Foundation Mainnet, which has a chain ID of `1`, this means that all transactions have a value of either `37` or `38`. + +The chain ID set in the genesis configuration file, under the `config` section, and is only used when the block number is above the one set at `eip155Block`. See the [quorum-examples genesis files](../genesis) for an example. It can be changed as many times as needed whilst the chain is below the `eip155Block` number and re-rerunning `geth init` - this will not delete or modify any current sync process or saved blocks! + +In Quorum, transactions are considered private if the `v` parameter is set to `37` or `38`, which clashes with networks which have a Chain ID of `1`. For this reason, Quorum will not run using chain ID `1` and will immediately quit if started with such a configuration from version 2.1.0 onwards. +If you are running a version prior to version 2.1.0, EIP-155 signing is not used, thus a chain ID of `1` was allowed; you will need to change this using `geth init` before running an updated version. + + +## ZSL Proof of Concept + +J.P. Morgan and the Zcash team partnered to create a proof of concept (POC) implementation of ZSL for Quorum, which enables the issuance of digital assets using ZSL-enabled public smart contracts (z-contracts). We refer to such digital assets as “z-tokens”. Z-tokens can be shielded from public view and transacted privately. Proof that a shielded transaction has been executed can be presented to a private contract, thereby allowing the private contract to update its state in response to shielded transactions that are executed using public z-contracts. + +This combination of Constellation/Tessera’s private contracts with ZSL’s z-contracts, allows obligations that arise from a private contract, to be settled using shielded transfers of z-tokens, while maintaining full privacy and confidentiality. + +For more information, see the [ZSL](../../ZSL) page of this wiki. + +## Configurable transaction size: + +Quorum allows operators of blockchains to increase maximum transaction size of accepted transactions via the genesis block. The Quorum default is currently increased to `64kb` from Ethereum's default `32kb` transaction size. This is configurable up to `128kb` by adding `txnSizeLimit` to the config section of the genesis file: + +``` json +"config": { + "chainId": 10, + "isQuorum":true. + ... + "txnSizeLimit": 128 +} +``` + diff --git a/docs/Privacy/Constellation/Constellation.md b/docs/Privacy/Constellation/Constellation.md new file mode 100644 index 000000000..d7d2907b0 --- /dev/null +++ b/docs/Privacy/Constellation/Constellation.md @@ -0,0 +1,60 @@ +# Constellation + +Constellation is a self-managing, peer-to-peer system in which each +node: + + - Hosts a number of NaCl (Curve25519) public/private key pairs. + + - Automatically discovers other nodes on the network after + synchronizing with as little as one other host. + + - Synchronizes a directory of public keys mapped to recipient hosts + with other nodes on the network. + + - Exposes a public API which allows other nodes to send encrypted + bytestrings to your node, and to synchronize, retrieving + information about the nodes that your node knows about. + + - Exposes a private API which: + + - Allows you to send a bytestring to one or more public keys, + returning a content-addressable identifier. This bytestring is + encrypted transparently and efficiently (at symmetric + encryption speeds) before being transmitted over the wire to + the correct recipient nodes (and only those nodes.) The + identifier is a hash digest of the encrypted payload that + every receipient node receives. Each recipient node also + receives a small blob encrypted for their public key which + contains the Master Key for the encrypted payload. + + - Allows you to receive a decrypted bytestring + based on an identifier. Payloads which your node has sent or + received can be decrypted and retrieved in this way. + + - Exposes methods for deletion, resynchronization, and other + management functions. + + - Supports a number of storage backends including LevelDB, + BerkeleyDB, SQLite, and Directory/Maildir-style file storage + suitable for use with any FUSE adapter, e.g. for AWS S3. + + - Uses mutually-authenticated TLS with modern settings and various trust + models including hybrid CA/tofu (default), tofu (think OpenSSH), and + whitelist (only some set of public keys can connect.) + + - Supports access controls like an IP whitelist. + +Conceptually, one can think of Constellation as an amalgamation of a +distributed key server, PGP encryption (using modern cryptography,) +and Mail Transfer Agents (MTAs.) + +Constellation's current primary application is to implement the +"privacy engine" of Quorum, a fork of Ethereum with support for +private transactions that function exactly as described in this +README. Private transactions in Quorum contain only a flag indicating +that they're private and the content-addressable identifier described +here. + +Constellation can be run stand-alone as a daemon via +`constellation-node`, or imported as a Haskell library, which allows +you to implement custom storage and encryption logic. diff --git a/docs/Privacy/Constellation/How constellation works.md b/docs/Privacy/Constellation/How constellation works.md new file mode 100644 index 000000000..891eac9ec --- /dev/null +++ b/docs/Privacy/Constellation/How constellation works.md @@ -0,0 +1,102 @@ +## How Constellation works + +Each Constellation node hosts some number of key pairs, and advertises +a publicly accessible FQDN/port for other hosts to connect to. + +Nodes can be started with a reference to existing nodes on the network +(with the `othernodes` configuration variable,) or without, in which +case some other node must later be pointed to this node to achieve +synchronization. + +When a node starts up, it will reach out to each node in `othernodes`, +and learn about the public keys they host, as well as other nodes in +the network. In short order, the node's public key directory will be +the same as that of all other nodes, and you can start addressing +messages to any of the known public keys. + +This is what happens when you use the `send` function of the Private +API to send the bytestring `foo` to the public key +`ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc=`: + + 1. You send a POST API request to the Private API socket like: + `{"payload": "foo", "from": "mypublickey", to: "ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="}` + + 2. The local node generates using `/dev/urandom` (or similar): + - A random Master Key (MK) and nonce + - A random recipient nonce + + 3. The local node encrypts the payload using NaCl `secretbox` using + the random MK and nonce. + + 4. The local node generates an MK container for each recipient + public key; in this case, simply one container for `ROAZ...`, + using NaCl `box` and the recipient nonce. + + NaCl `box` works by deriving a shared key based + on your private key and the recipient's public key. This is known + as elliptic curve key agreement. + + Note that the sender public key and recipient public key we + specified above aren't enough to perform the + encryption. Therefore, the node will check to see that it is + actually hosting the private key that corresponds to the given + public key before generating an MK container for each recipient + based on SharedKey(yourprivatekey, recipientpublickey) and the + recipient nonce. + + We now have: + + - An encrypted payload which is `foo` encrypted with the random + MK and a random nonce. This is the same for all recipients. + + - A random recipient nonce that also is the same for all + recipients. + + - For each recipient, the MK encrypted with the + shared key of your private key and their public key. This + MK container is unique per recipient, and is only transmitted to + that recipient. + + 5. For each recipient, the local node looks up the recipient host, + and transmits to it: + + - The sender's (your) public key + + - The encrypted payload and nonce + + - The MK container for that recipient and the recipient nonce + + 6. The recipient node returns a SHA3-512 hash digest of the + encrypted payload, which represents its storage address. + + (Note that it is not possible for the sender to dictate the + storage address. Every node generates it independently by hashing + the encrypted payload.) + + 7. The local node stores the payload locally, generating the same + hash digest. + + 8. The API call returns successfully once all nodes have confirmed + receipt and storage of the payload, and returned a hash digest. + +Now, through some other mechanism, you'll inform the recipient that +they have a payload waiting for them with the identifier `owqkrokwr`, +and they will make a call to the `receive` method of their Private +API: + + 1. Make a call to the Private API socket `receive` method: + `{"key": "qrqwrqwr"}` + + 2. The local node will look in its storage for the key `qrqwrqwr`, + and abort if it isn't found. + + 3. When found, the node will use the information about the sender as + well as its private key to derive SharedKey(senderpublickey, + yourprivatekey) and decrypt the MK container using NaCl `box` + with the recipient nonce. + + 4. Using the decrypted MK, the local node will decrypt the encrypted + payload using NaCl `secretbox` using the main nonce. + + 5. The API call returns the decrypted data. + diff --git a/docs/Privacy/Constellation/Installation & Running.md b/docs/Privacy/Constellation/Installation & Running.md new file mode 100644 index 000000000..122aa5854 --- /dev/null +++ b/docs/Privacy/Constellation/Installation & Running.md @@ -0,0 +1,41 @@ +## Installation + +### Prerequisites + + 1. Install supporting libraries: + - Ubuntu: `apt-get install libdb-dev libleveldb-dev libsodium-dev zlib1g-dev libtinfo-dev` + - Red Hat: `dnf install libdb-devel leveldb-devel libsodium-devel zlib-devel ncurses-devel` + - MacOS: `brew install berkeley-db leveldb libsodium` + +### Downloading precompiled binaries + +Constellation binaries for most major platforms can be downloaded [here](https://github.com/jpmorganchase/constellation/releases). + +### Installation from source + + 1. First time only: Install Stack: + - Linux: `curl -sSL https://get.haskellstack.org/ | sh` + - MacOS: `brew install haskell-stack` + + 2. First time only: run `stack setup` to install GHC, the Glasgow + Haskell Compiler + + 3. Run `stack install` + +## Generating keys + + 1. To generate a key pair "node", run `constellation-node --generatekeys=node` + + If you choose to lock the keys with a password, they will be encrypted using + a master key derived from the password using Argon2id. This is designed to be + a very expensive operation to deter password cracking efforts. When + constellation encounters a locked key, it will prompt for a password after + which the decrypted key will live in memory until the process ends. + +## Running + + 1. Run `constellation-node ` or specify configuration + variables as command-line options (see `constellation-node --help`) + +Please refer to the [Constellation client Go library](../constellation-go) +for an example of how to use Constellation. diff --git a/docs/Privacy/Constellation/Sample Configuration.md b/docs/Privacy/Constellation/Sample Configuration.md new file mode 100644 index 000000000..9521c34b1 --- /dev/null +++ b/docs/Privacy/Constellation/Sample Configuration.md @@ -0,0 +1,262 @@ +``` yaml +##### +## Constellation configuration file example +## ---------------------------------------- +## Every option listed here can also be specified on the command line, e.g. +## `constellation-node --url=http://www.foo.com --port 9001 ...` +## (lists are given using comma-separated strings) +## If both command line parameters and a configuration file are given, the +## command line options will take precedence. +## +## The only strictly necessary option is `port`, however it's recommended to +## set at least the following: +## +## --url The URL to advertise to other nodes (reachable by them) +## --port The local port to listen on +## --workdir The folder to put stuff in (default: .) +## --socket IPC socket to create for access to the Private API +## --othernodes "Boot nodes" to connect to to discover the network +## --publickeys Public keys hosted by this node +## --privatekeys Private keys hosted by this node (in corresponding order) +## +## Example usage: +## +## constellation-node --workdir=data --generatekeys=foo +## (To generate a keypair foo in the data directory) +## +## constellation-node --url=https://localhost:9000/ \ +## --port=9000 \ +## --workdir=data \ +## --socket=constellation.ipc \ +## --othernodes=https://localhost:9001/ \ +## --publickeys=foo.pub \ +## --privatekeys=foo.key +## +## constellation-node sample.conf +## +## constellation-node --port=9002 sample.conf +## (This overrides the port value given in sample.conf) +## +## Note on defaults: "Default:" below indicates the value that will be assumed +## if the option is not present either in the configuration file or as a command +## line parameter. +## +## Note about security: In the default configuration, Constellation will +## automatically generate TLS certificates and trust other nodes' certificates +## when they're first encountered (trust-on-first-use). See the documentation +## for tlsservertrust and tlsclienttrust below. To disable TLS entirely, e.g. +## when using Constellation in conjunction with a VPN like WireGuard, set tls to +## off. +##### + +## Externally accessible URL for this node's public API (this is what's +## advertised to other nodes on the network, and must be reachable by them.) +url = "http://127.0.0.1:9001/" + +## Port to listen on for the public API. +port = 9001 + +## Directory in which to put and look for other files referenced here. +## +## Default: The current directory +workdir = "data" + +## Socket file to use for the private API / IPC. If this is commented out, +## the private API will not be accessible. +## +## Default: Not set +socket = "constellation.ipc" + +## Initial (not necessarily complete) list of other nodes in the network. +## Constellation will automatically connect to other nodes not in this list +## that are advertised by the nodes below, thus these can be considered the +## "boot nodes." +## +## Default: [] +othernodes = ["http://127.0.0.1:9000/"] + +## The set of public keys this node will host. +## +## Default: [] +publickeys = ["foo.pub"] + +## The corresponding set of private keys. These must correspond to the public +## keys listed above. +## +## Default: [] +privatekeys = ["foo.key"] + +## Optional comma-separated list of paths to public keys to add as recipients +## for every transaction sent through this node, e.g. for backup purposes. +## These keys must be advertised by some Constellation node on the network, i.e. +## be in a node's publickeys/privatekeys lists. +## +## Default: [] +alwayssendto = [] + +## Optional file containing the passwords needed to unlock the given privatekeys +## (the file should contain one password per line -- add an empty line if any +## one key isn't locked.) +## +## Default: Not set +# passwords = "passwords" + +## Storage engine used to save payloads and related information. Options: +## - bdb:path (BerkeleyDB) +## - dir:path (Directory/file storage - can be used with e.g. FUSE-mounted +## file systems.) +## - leveldb:path (LevelDB - experimental) +## - memory (Contents are cleared when Constellation exits) +## - sqlite:path (SQLite - experimental) +## +## Default: "dir:storage" +storage = "dir:storage" + +## Verbosity level (each level includes all prior levels) +## - 0: Only fatal errors +## - 1: Warnings +## - 2: Informational messages +## - 3: Debug messages +## +## At the command line this can be specified using -v0, -v1, -v2, -v3, or +## -v (2) and -vv (3). +## +## Default: 1 +verbosity = 1 + +## Optional IP whitelist for the public API. If unspecified/empty, +## connections from all sources will be allowed (but the private API remains +## accessible only via the IPC socket above.) To allow connections from +## localhost when a whitelist is defined, e.g. when running multiple +## Constellation nodes on the same machine, add "127.0.0.1" and "::1" to +## this list. +## +## Default: Not set +# ipwhitelist = ["10.0.0.1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"] + +## TLS status. Options: +## +## - strict: All connections to and from this node must use TLS with mutual +## authentication. See the documentation for tlsservertrust and +## tlsclienttrust below. +## - off: Mutually authenticated TLS is not used for in- and outbound +## connections, although unauthenticated connections to HTTPS hosts are +## still possible. This should only be used if another transport security +## mechanism like WireGuard is in place. +## +## Default: "strict" +tls = "strict" + +## Path to a file containing the server's TLS certificate in Apache format. +## This is used to identify this node to other nodes in the network when they +## connect to the public API. +## +## This file will be auto-generated if it doesn't exist. +## +## Default: "tls-server-cert.pem" +tlsservercert = "tls-server-cert.pem" + +## List of files that constitute the CA trust chain for the server certificate. +## This can be empty for auto-generated/non-PKI-based certificates. +## +## Default: [] +tlsserverchain = [] + +## The private key file for the server TLS certificate. +## +## This file will be auto-generated if it doesn't exist. +## +## Default: "tls-server-key.pem" +tlsserverkey = "tls-server-key.pem" + +## TLS trust mode for the server. This decides who's allowed to connect to it. +## Options: +## +## - whitelist: Only nodes that have previously connected to this node and +## been added to the tlsknownclients file below will be allowed to connect. +## This mode will not add any new clients to the tlsknownclients file. +## +## - tofu: (Trust-on-first-use) Only the first node that connects identifying +## as a certain host will be allowed to connect as the same host in the +## future. Note that nodes identifying as other hosts will still be able +## to connect -- switch to whitelist after populating the tlsknownclients +## list to restrict access. +## +## - ca: Only nodes with a valid certificate and chain of trust to one of +## the system root certificates will be allowed to connect. The folder +## containing trusted root certificates can be overriden with the +## SYSTEM_CERTIFICATE_PATH environment variable. +## +## - ca-or-tofu: A combination of ca and tofu: If a certificate is valid, +## it is always allowed and added to the tlsknownclients list. If it is +## self-signed, it will be allowed only if it's the first certificate this +## node has seen for that host. +## +## - insecure-no-validation: Any client can connect, however they will still +## be added to the tlsknownclients file. +## +## Default: "tofu" +tlsservertrust = "tofu" + +## TLS known clients file for the server. This contains the fingerprints of +## public keys of other nodes that are allowed to connect to this one. +## +## Default: "tls-known-clients" +tlsknownclients = "tls-known-clients" + +## Path to a file containing the client's TLS certificate in Apache format. +## This is used to identify this node to other nodes in the network when it is +## connecting to their public APIs. +## +## This file will be auto-generated if it doesn't exist. +## +## Default: "tls-client-cert.pem" +tlsclientcert = "tls-client-cert.pem" + +## List of files that constitute the CA trust chain for the client certificate. +## This can be empty for auto-generated/non-PKI-based certificates. +## +## Default: [] +tlsclientchain = [] + +## The private key file for the client TLS certificate. +## +## This file will be auto-generated if it doesn't exist. +## +## Default: "tls-client-key.pem" +tlsclientkey = "tls-client-key.pem" + +## TLS trust mode for the client. This decides which servers it will connect to. +## Options: +## +## - whitelist: This node will only connect to servers it has previously seen +## and added to the tlsknownclients file below. This mode will not add +## any new servers to the tlsknownservers file. +## +## - tofu: (Trust-on-first-use) This node will only connect to the same +## server for any given host. (Similar to how OpenSSH works.) +## +## - ca: The node will only connect to servers with a valid certificate and +## chain of trust to one of the system root certificates. The folder +## containing trusted root certificates can be overriden with the +## SYSTEM_CERTIFICATE_PATH environment variable. +## +## - ca-or-tofu: A combination of ca and tofu: If a certificate is valid, +## it is always allowed and added to the tlsknownservers list. If it is +## self-signed, it will be allowed only if it's the first certificate this +## node has seen for that host. +## +## - insecure-no-validation: This node will connect to any server, regardless +## of certificate, however it will still be added to the tlsknownservers +## file. +## +## Default: "ca-or-tofu" +tlsclienttrust = "ca-or-tofu" + +## TLS known servers file for the client. This contains the fingerprints of +## public keys of other nodes that this node has encountered. +## +## Default: "tls-known-servers" +tlsknownservers = "tls-known-servers" +``` + diff --git a/docs/Privacy/Constellation/constellation-go.md b/docs/Privacy/Constellation/constellation-go.md new file mode 100644 index 000000000..07ad1b8ea --- /dev/null +++ b/docs/Privacy/Constellation/constellation-go.md @@ -0,0 +1,160 @@ +```go +package constellation + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/tv42/httpunix" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "strings" + "time" +) + +func launchNode(cfgPath string) (*exec.Cmd, error) { + cmd := exec.Command("constellation-node", cfgPath) + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + go io.Copy(os.Stderr, stderr) + if err := cmd.Start(); err != nil { + return nil, err + } + time.Sleep(100 * time.Millisecond) + return cmd, nil +} + +func unixTransport(socketPath string) *httpunix.Transport { + t := &httpunix.Transport{ + DialTimeout: 1 * time.Second, + RequestTimeout: 5 * time.Second, + ResponseHeaderTimeout: 5 * time.Second, + } + t.RegisterLocation("c", socketPath) + return t +} + +func unixClient(socketPath string) *http.Client { + return &http.Client{ + Transport: unixTransport(socketPath), + } +} + +func RunNode(socketPath string) error { + c := unixClient(socketPath) + res, err := c.Get("http+unix://c/upcheck") + if err != nil { + return err + } + if res.StatusCode == 200 { + return nil + } + return errors.New("Constellation Node API did not respond to upcheck request") +} + +type Client struct { + httpClient *http.Client +} + +func (c *Client) doJson(path string, apiReq interface{}) (*http.Response, error) { + buf := new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(apiReq) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", "http+unix://c/"+path, buf) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + res, err := c.httpClient.Do(req) + if err == nil && res.StatusCode != 200 { + return nil, fmt.Errorf("Non-200 status code: %+v", res) + } + return res, err +} + +func (c *Client) SendPayload(pl []byte, b64From string, b64To []string) ([]byte, error) { + buf := bytes.NewBuffer(pl) + req, err := http.NewRequest("POST", "http+unix://c/sendraw", buf) + if err != nil { + return nil, err + } + if b64From != "" { + req.Header.Set("c11n-from", b64From) + } + req.Header.Set("c11n-to", strings.Join(b64To, ",")) + req.Header.Set("Content-Type", "application/octet-stream") + res, err := c.httpClient.Do(req) + + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Non-200 status code: %+v", res) + } + + return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body)) +} + +func (c *Client) SendSignedPayload(signedPayload []byte, b64To []string) ([]byte, error) { + buf := bytes.NewBuffer(signedPayload) + req, err := http.NewRequest("POST", "http+unix://c/sendsignedtx", buf) + if err != nil { + return nil, err + } + + req.Header.Set("c11n-to", strings.Join(b64To, ",")) + req.Header.Set("Content-Type", "application/octet-stream") + res, err := c.httpClient.Do(req) + + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Non-200 status code: %+v", res) + } + + return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body)) +} + +func (c *Client) ReceivePayload(key []byte) ([]byte, error) { + req, err := http.NewRequest("GET", "http+unix://c/receiveraw", nil) + if err != nil { + return nil, err + } + req.Header.Set("c11n-key", base64.StdEncoding.EncodeToString(key)) + res, err := c.httpClient.Do(req) + + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Non-200 status code: %+v", res) + } + + return ioutil.ReadAll(res.Body) +} + +func NewClient(socketPath string) (*Client, error) { + return &Client{ + httpClient: unixClient(socketPath), + }, nil +} +``` diff --git a/docs/Privacy/Tessera/Configuration/Configuration Overview.md b/docs/Privacy/Tessera/Configuration/Configuration Overview.md new file mode 100644 index 000000000..12d60a0db --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Configuration Overview.md @@ -0,0 +1,183 @@ +# Configuration file + +A `.json` file including required configuration details must be provided using the `-configfile` command-line property when starting Tessera. + +Many configuration options can be overridden using the command-line. See the [Using CLI to override config](../Using%20CLI%20to%20override%20config) page for more information. + +## Configuration options +The configuration options are explained in more detail in this section. Configuration options that require more than a brief explanation are covered in separate pages. + +### Cryptographic Keys +See [Keys page](../Keys). + +### Whitelist +If set to true, the `peers` list will be used as the whitelisted urls for the Tessera node: +``` +"useWhiteList": true, +``` + +--- + +### Database +Tessera's database uses JDBC to connect to an external database. Any valid JDBC URL may be specified, refer to your providers details to construct a valid JDBC URL. + +``` +"jdbc": { + "url": "[JDBC URL]", + "username": "[JDBC Username]", + "password": "[JDBC Password]" +} +``` + +--- + +### Server +> **For Tessera versions prior to 0.8:** See [Legacy Server Settings](../Legacy%20server%20settings). + +To allow for a greater level of control, Tessera's API has been separated into distinct groups. Each group is only accessible over a specific server type. Tessera can be started with different combinations of these servers depending on the functionality required. This is defined in the configuration and determines the APIs that are available and how they are accessed. + +The possible server types are: + +- `P2P` - Tessera uses this server to communicate with other Transaction Managers (the URI for this server can be shared with other nodes to be used in their `peer` list - see below) +- `Q2T` - This server is used for communications between Tessera and its corresponding Quorum node +- `ENCLAVE` - If using a remote enclave, this defines the connection details for the remote enclave server (see the [Enclave docs](../../Tessera%20Services/Enclave#types-of-enclave) for more info) +- `ThirdParty` - This server is used to expose certain Transaction Manager functionality to external services such as Quorum.js +- `ADMIN` - This server is used for configuration management. It is intended for use by the administrator of the Tessera node and is not recommended to be advertised publicly + +The servers to be started are provided as a list: +``` +"serverConfigs": [ + ...... +] +``` + +Each server is individually configurable and can advertise over HTTP, HTTPS or a Unix Socket. The format of an individual server config is slightly different between Tessera v0.9 and v0.8: + +#### Server configuration (v0.9) +HTTP: +``` +{ + "app": "", + "enabled": , + "serverUri":"http://[host]:[port]/[path] + "communicationType" : , // "REST" or "GRPC" +} +``` +HTTPS: +``` +{ + "app": "", + "enabled": , + "serverUri":"https://[host]:[port]/[path] + "communicationType" : , // "REST" or "GRPC" + "sslConfig": { + ...... + } +} +``` +Unix Socket: +``` +{ + "app": "", + "enabled": , + "serverUri":"unix://[path], + "communicationType" : "REST" +} +``` + +#### Server configuration (v0.8) +HTTP: +``` +{ + "app": "", + "enabled": , + "serverSocket":{ + "type": "INET", + "port": , //The port to advertise and bind on (if binding address not set) + "hostName": // The hostname to advertise and bind on (if binding address not set) + }, + "communicationType" : , // "REST" or "GRPC" + "bindingAddress": //An address to bind the server to that overrides the one defined above +} +``` + +HTTPS: +``` +{ + "app": "", + "enabled": , + "serverSocket":{ + "type": "INET", + "port": , //The port to advertise and bind on (if binding address not set) + "hostName": // The hostname to advertise and bind on (if binding address not set) + }, + "communicationType" : , // "REST" or "GRPC" + "bindingAddress": , //An address to bind the server to that overrides the one defined above + "sslConfig": { + ...... + } +} +``` + +Unix Socket: +``` +{ + "app": "", + "enabled": , + "serverSocket":{ + "type":"UNIX", + "path": //the path of the unix socket to create + }, + "communicationType" : "UNIX_SOCKET" +} +``` + +### TLS/SSL: server sub-config +See [TLS/SSL](../TLS) page. + +### InfluxDB Config: server sub-config +Configuration details to allow Tessera to record monitoring data to a running InfluxDB instance. +``` +"influxConfig": { + "hostName": "[Hostname of Influx instance]", + "port": "[Port of Influx instance]", + "pushIntervalInSecs": "[How often to push data to InfluxDB]", + "dbName": "[Name of InfluxDB]" +} +``` + +--- + +### Peers +A list of URLs used by Tessera to communicate with other nodes. Peer info is shared between nodes during runtime (however, please note the section on `Peer Discovery` below). +``` +"peer": [ + { + "url": "http://myhost.com:9000" + }, + { + "url": "http://myhost.com:9001" + }, + { + "url": "http://myhost.com:9002" + } +] +``` + +### Disabling peer discovery +If peer discovery is disabled, then **only** peers defined in the configuration file will be communicated with; any peers notified by other nodes will be ignored. This allows nodes to be 'locked down' if desired. +``` +"disablePeerDiscovery": true +``` + +--- + +### Always-send-to +It is possible to configure a node that will be sent a copy of every transaction, even if it is not specified as a party to the transaction. This could be used, for example, to send a copy of every transaction to a node for audit purposes. Specify the public keys to forward transactions onto, and these will be included as if you had specified them on the `privateFor` field to start with. + +``` +"alwaysSendTo":["", ""] +``` + +--- + diff --git a/docs/Privacy/Tessera/Configuration/Keys.md b/docs/Privacy/Tessera/Configuration/Keys.md new file mode 100644 index 000000000..f91cf5d0e --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Keys.md @@ -0,0 +1,185 @@ +Tessera uses cryptographic keys to provide transaction privacy. + +You can use existing private/public key pairs as well as use Tessera to generate new key pairs for you. See [Generating & securing keys](../../Tessera%20Services/Keys/Keys) for more info. +``` +"keys": { + "passwords": [], + "passwordFile": "Path", + "azureKeyVaultConfig": { + "url": "Url" + }, + "hashicorpKeyVaultConfig": { + "url": "Url", + "approlePath": "String", + "tlsKeyStorePath": "Path", + "tlsTrustStorePath": "Path" + }, + "keyData": [ + { + //The data for a private/public key pair + } + ] +} +``` + +## KeyData +Key pairs can be provided in several ways: + +#### 1. Direct key pairs +Direct key pairs are convenient but are the least secure configuration option available, as you expose your private key in the configuration file. More secure options are available and preferable for production environments. + +The key pair data is provided in plain text in the configfile: +``` +"keys": { + "keyData": [ + { + "privateKey": "yAWAJjwPqUtNVlqGjSrBmr1/iIkghuOh1803Yzx9jLM=", + "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + } + ] +} +``` + +#### 2. Inline key pairs +The public key is provided in plain text. The private key is provided through additional config: +``` +"keys": { + "keyData": [ + { + "config": { + "data": { + "bytes": "yAWAJjwPqUtNVlqGjSrBmr1/iIkghuOh1803Yzx9jLM=" + }, + "type": "unlocked" + }, + "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + } + ] +} +``` + +This allows for the use of Argon2 password-secured private keys by including the corresponding Argon2 settings in the additional config: + +``` +"keys": { + "passwords": ["password"], + "keyData": [ + { + "config": { + "data": { + "aopts": { + "variant": "id", + "memory": 1048576, + "iterations": 10, + "parallelism": 4, + }, + "snonce": "x3HUNXH6LQldKtEv3q0h0hR4S12Ur9pC", + "asalt": "7Sem2tc6fjEfW3yYUDN/kSslKEW0e1zqKnBCWbZu2Zw=", + "sbox": "d0CmRus0rP0bdc7P7d/wnOyEW14pwFJmcLbdu2W3HmDNRWVJtoNpHrauA/Sr5Vxc" + }, + "type": "argon2sbox" + }, + "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + } + ] +} +``` + +#### 3. Azure Key Vault key pairs +The keys in the pair are stored as secrets in an Azure Key Vault. This requires providing the vault url and the secret IDs for both keys: +``` +"keys": { + "azureKeyVaultConfig": { + "url": "https://my-vault.vault.azure.net" + }, + "keyData": [ + { + "azureVaultPrivateKeyId": "Key", + "azureVaultPublicKeyId": "Pub", + "azureVaultPublicKeyVersion": "bvfw05z4cbu11ra2g94e43v9xxewqdq7", + "azureVaultPrivateKeyVersion": "0my1ora2dciijx5jq9gv07sauzs5wjo2" + } + ] +} +``` + +This example configuration will retrieve the specified versions of the secrets `Key` and `Pub` from the key vault with DNS name `https://my-vault.vault.azure.net`. If no version is specified then the latest version of the secret is retrieved. + +> Environment variables must be set if using an Azure Key Vault, for more information see [Setting up an Azure Key Vault](../../Tessera%20Services/Keys/Setting%20up%20an%20Azure%20Key%20Vault) + +#### 4. Hashicorp Vault key pairs +The keys in the pair are stored as a secret in a Hashicorp Vault. Additional configuration can also be provided if the Vault is configured to use TLS and if the AppRole auth method is being used at a different path to the default (`approle`): +``` +"hashicorpKeyVaultConfig": { + "url": "https://localhost:8200", + "tlsKeyStorePath": "/path/to/keystore.jks", + "tlsTrustStorePath": "/path/to/truststore.jks", + "approlePath": "not-default", +}, +"keyData": [ + { + "hashicorpVaultSecretEngineName": "engine", + "hashicorpVaultSecretName": "secret", + "hashicorpVaultSecretVersion": 1, + "hashicorpVaultPrivateKeyId": "privateKey", + "hashicorpVaultPublicKeyId": "publicKey", + } +] +``` + +This example configuration will retrieve version 1 of the secret `engine/secret` from Vault and its corresponding values for `privateKey` and `publicKey`. + +If no `hashicorpVaultSecretVersion` is provided then the latest version for the secret will be retrieved by default. + +Tessera requires TLS certificates and keys to be stored in `.jks` Java keystore format. If the `.jks` files are password protected then the following environment variables must be set: +* `HASHICORP_CLIENT_KEYSTORE_PWD` +* `HASHICORP_CLIENT_TRUSTSTORE_PWD` + +> If using a Hashicorp Vault additional environment variables must be set and a version 2 K/V secret engine must be enabled. For more information see [Setting up a Hashicorp Vault](../../Tessera%20Services/Keys/Setting%20up%20a%20Hashicorp%20Vault). + +#### 5. Filesystem key pairs +The keys in the pair are stored in files: +``` +"keys": { + "passwordFile": "/path/to/passwords", + "keyData": [ + { + "privateKeyPath": "/path/to/privateKey.key", + "publicKeyPath": "/path/to/publicKey.pub" + } + ] +} +``` +The contents of the public key file must contain the public key only, e.g.: +``` +/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc= +``` + +The contents of the private key file must contain the private key in the config format, e.g.: +``` +{ + "type" : "unlocked", + "data" : { + "bytes" : "DK0HDgMWJKtZVaP31mPhk6TJNACfVzz7VZv2PsQZeKM=" + } +} +``` +## Multiple Keys +If wished, multiple key pairs can be specified for a Tessera node. In this case, any one of the public keys can be used to address a private transaction to that node. Tessera will sequentially try each key to find one that can decrypt the payload. This can be used, for example, to simplify key rotation. + +Note that multiple key pairs can only be set up within the configuration file, not via separate filesystem key files. + +## Viewing the keys registered for a node +An ADMIN API endpoint `/config/keypairs` exists to allow you to view the public keys of the key pairs currently in use by your Tessera node. This requires configuring an ADMIN server in the node's configuration file, as described in [Configuration Overview](../Configuration%20Overview). + +A sample response for the request `adminhost:port/config/keypairs` is: +```json +[ + { + "publicKey" : "oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8=" + }, + { + "publicKey" : "ABn6zhBth2qpdrJXp98IvjExV212ALl3j4U//nj4FAI=" + } +] +``` diff --git a/docs/Privacy/Tessera/Configuration/Legacy server settings.md b/docs/Privacy/Tessera/Configuration/Legacy server settings.md new file mode 100644 index 000000000..7801e3f3e --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Legacy server settings.md @@ -0,0 +1,50 @@ +**Important** + +Legacy server settings were part of Tessera v0.5, v0.6 and v0.7. They have deprecated for, but still work with, v0.8 and v0.9 and may be removed for any future versions. + +--- + +The server settings are defined as following: + +``` +"server": { + "hostName": "", + "port": "", + "bindingAddress": "", + "sslConfig": <...>, + "influxConfig": <...> +} +``` + +
+
+ +If the address to advertise keys on is the same as the address you wish to bind to, then the `bindingAddress` may be omitted, for example: +``` +"server": { + "hostName": "http://myhost.com", + "port": 9999, + "sslConfig": <...>, + "influxConfig": <...> +} +``` + +--- + +**InfluxDB Config** + +Configuration details to allow Tessera to record monitoring data to a running InfluxDB instance. +``` +"influxConfig": { + "hostName": "[Hostname of Influx instance]", + "port": "[Port of Influx instance]", + "pushIntervalInSecs": "[How often to push data to InfluxDB]", + "dbName": "[Name of InfluxDB]" +} +``` + +### Unix socket file +Path to the Unix domain socket file used to communicate between Quorum and Tessera. +``` +"unixSocketFile" : "/path/to/socketfile.ipc" +``` diff --git a/docs/Privacy/Tessera/Configuration/Sample Configuration.md b/docs/Privacy/Tessera/Configuration/Sample Configuration.md new file mode 100644 index 000000000..8b77537e3 --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Sample Configuration.md @@ -0,0 +1,9 @@ +Tessera configuration varies by version as new features are added or changed. Below is a list of sample configurations that show a possible structure. There may be more features that are not included in the sample; a full list of features can be found [here](../Configuration%20Overview). + +## Samples + +| Version | +| ------------- | +| [0.9 - latest release](../Tessera%20v0.9%20sample%20settings) | +| [0.8](../Tessera%20v0.8%20sample%20settings) | +| [0.7.3](../Tessera%20v0.7.3%20sample%20settings) | diff --git a/docs/Privacy/Tessera/Configuration/TLS.md b/docs/Privacy/Tessera/Configuration/TLS.md new file mode 100644 index 000000000..cb92264eb --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/TLS.md @@ -0,0 +1,168 @@ +### Usage +Communications via TLS/SSL can be enabled by setting `"tls": "STRICT"`. + +If the value is set to `"OFF"`, the rest of the SSL configuration will not be considered. + +!!! warning + If using TLS make sure to update the hostname of the node to use `https` instead of `http` + +```json +{ + "sslConfig": { + "tls": "[Authentication mode : OFF,STRICT]", + + "serverTrustMode": "[Possible values: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE]", + "serverKeyStore": "[Path to server keystore]", + "serverKeyStorePassword": "[Password required for server KeyStore]", + "serverTrustStore": "[Server trust store path]", + "serverTrustStorePassword": "[Password required for server trust store]", + "serverTlsKeyPath": "[Path to server TLS key path]", + "serverTlsCertificatePath": "[Path to server TLS cert path]", + "serverTrustCertificates": [ + "[Array of truststore certificates if no truststore is defined.]" + ], + + "clientTrustMode": "[Possible values: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE]", + "clientKeyStore": "[Path to client keystore. The keystore that is used when communicating to other nodes.]", + "clientKeyStorePassword": "[Password required for client KeyStore]", + "clientTrustStore": "[Path to client TrustStore]", + "clientTrustStorePassword": "[Password required for client trust store]", + "clientTlsKeyPath": "[Path to client TLS Key]", + "clientTlsCertificatePath": "[Path to client TLS cert]", + "clientTrustCertificates": [ + "[Array of truststore certificates if no truststore is defined.]" + ], + + "knownClientsFile": "[TLS known clients file for the server. This contains the fingerprints of public keys of other nodes that are allowed to connect to this one.]", + "knownServersFile": "[TLS known servers file for the client. This contains the fingerprints of public keys of other nodes that this node has encountered.]", + "generateKeyStoreIfNotExisted": "[boolean]", + "environmentVariablePrefix": "[Prefix to uniquely identify environment variables for this particular server ssl config]" + } +} +``` + +When SSL is enabled, each node will need to have certificates and keys defined for both client-side and server-side. These can be defined in multiple ways: + +1. Secured & unsecured `.jks` (Java keystore) format files + * `serverKeyStore`, `serverKeyStorePassword`, `serverTrustStore`, `serverTrustStorePassword` + * `clientKeyStore`, `clientKeyStorePassword`, `clientTrustStore`, `clientTrustStorePassword` +2. `.pem` format certificate and key files + * `serverTlsKeyPath`, `serverTlsCertificatePath`, `serverTrustCertificates` + * `clientTlsKeyPath`, `clientTlsCertificatePath`, `clientTrustCertificates` + +`.jks` files take precedence over `.pem` files if both are provided for client-side or server-side. + +#### Keystores +##### Passwords +Passwords for secured `.jks` keystores can be provided in multiple ways, and in the following order of precedence: +1. *Prefixed* environment variables + * `_TESSERA_SERVER_KEYSTORE_PWD`, `_TESSERA_SERVER_TRUSTSTORE_PWD` + * `_TESSERA_CLIENT_KEYSTORE_PWD`, `_TESSERA_CLIENT_TRUSTSTORE_PWD` + +2. Config file + * `serverKeyStorePassword`, `serverTrustStorePassword` + * `clientKeyStorePassword`, `clientTrustStorePassword` + +3. *Global* environment variables + * `TESSERA_SERVER_KEYSTORE_PWD`, `TESSERA_SERVER_TRUSTSTORE_PWD` + * `TESSERA_CLIENT_KEYSTORE_PWD`, `TESSERA_CLIENT_TRUSTSTORE_PWD` + +The *global* environment variables, if set, are applied to all server configs defined in the configfile (i.e. if a P2P and ADMIN server are both configured with TLS then the values set for the global environment variables will be used for both). These values are ignored if the passwords are also provided in the configfile or as prefixed environment variables. + +The *prefixed* environment variables are only applied to the servers with that `environmentVariablePrefix` value defined in their config. This allows, for example, a P2P and ADMIN server to be configured with different prefixes, `P2P` and `ADMIN`. Different keystores can then be used for each server and the individual passwords provided with `P2P_<...>` and `ADMIN_<...>`. + +##### Generating keystores +If keystores do not already exist, Tessera can generate `.jks` (Java keystore) files for use with non-CA Trust Modes (see Trust Modes). + +By setting `"generateKeyStoreIfNotExisted": "true"`, Tessera will check whether files already exist at the paths provided in the `serverKeyStore` and `clientKeyStore` config values. If the files do not exist: + +1. New keystores will be generated and saved at the `serverKeyStore` and `clientKeyStore` paths +2. The keystores will be secured using the corresponding passwords if they are provided (see Passwords) + +#### PEM files +Below is a config sample for using the `.pem` file format: +```json +"sslConfig" : { + "tls" : "STRICT", + "generateKeyStoreIfNotExisted" : "false", + "serverTlsKeyPath" : "server-key.pem", + "serverTlsCertificatePath" : "server-cert.pem", + "serverTrustCertificates" : ["server-trust.pem"] + "serverTrustMode" : "CA", + "clientTlsKeyPath" : "client-key.pem", + "clientTlsCertificatePath" : "client-cert.pem", + "clientTrustCertificates" : ["client-trust.pem"] + "clientTrustMode" : "TOFU", + "knownClientsFile" : "knownClients", + "knownServersFile" : "knownServers" +} +``` + +#### Trust Modes +The Trust Mode for both client and server must also be specified. Multiple trust modes are supported: `TOFU`, `WHITELIST`, `CA`, `CA_OR_TOFU`, and `NONE`. + +* `TOFU` (Trust-on-first-use) + Only the first node that connects identifying as a certain host will be allowed to connect as the same host in the future. When connecting for the first time, the host and its certificate will be added to `knownClientsFile` (for server), or `knownServersFile` (for client). These files will be generated if not already existed, using the values specified in `knownClientsFile` and `knownServersFile`. + + A config sample for `TOFU` trust mode is: + + ```json + "sslConfig" : { + "tls" : "STRICT", + "generateKeyStoreIfNotExisted" : "true", + "serverKeyStore" : "server-keystore", + "serverKeyStorePassword" : "tessera", + "serverTrustMode" : "TOFU", + "clientKeyStore" : "client-keystore", + "clientKeyStorePassword" : "tessera", + "clientTrustMode" : "TOFU", + "knownClientsFile" : "knownClients", + "knownServersFile" : "knownServers" + } + ``` + +* `WHITELIST` + Only nodes that have previously connected to this node and have been added to the `knownClients` file will be allowed to connect. Similarly, this node will only be allowed to make connections to nodes that have been added to the `knownServers` file. This trust mode will not add new entries to the `knownClients` or `knownServers` files. + + With this trust mode, the whitelist files (`knownClientsFile` and `knownServersFile`) must be provided. + + A config sample for `WHITELIST` trust mode is: + + ```json + "sslConfig" : { + "tls" : "STRICT", + "generateKeyStoreIfNotExisted" : "true", + "serverKeyStore" : "server-keystore", + "serverKeyStorePassword" : "tessera", + "serverTrustMode" : "WHITELIST", + "clientKeyStore" : "client-keystore", + "clientKeyStorePassword" : "tessera", + "clientTrustMode" : "WHITELIST", + "knownClientsFile" : "knownClients", + "knownServersFile" : "knownServers" + } + ``` + +* `CA` + Only nodes with a valid certificate and chain of trust are allowed to connect. For this trust mode, trust stores must be provided and must contain a list of trust certificates. + + A config sample for `CA` trust mode is: + + ```json + "sslConfig" : { + "tls" : "STRICT", + "generateKeyStoreIfNotExisted" : "false", //You can't generate trust stores when using CA + "serverKeyStore" : "server-keystore", + "serverKeyStorePassword" : "tessera", + "serverTrustStore" : "server-truststore", + "serverTrustStorePassword" : "tessera", + "serverTrustMode" : "CA", + "clientKeyStore" : "client-keystore", + "clientKeyStorePassword" : "tessera", + "clientTrustStore" : "client-truststore", + "clientTrustStorePassword" : "tessera", + "clientTrustMode" : "CA", + "knownClientsFile" : "knownClients", + "knownServersFile" : "knownServers" + } + ``` diff --git a/docs/Privacy/Tessera/Configuration/Tessera v0.7.3 sample settings.md b/docs/Privacy/Tessera/Configuration/Tessera v0.7.3 sample settings.md new file mode 100644 index 000000000..4c0ed36b5 --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Tessera v0.7.3 sample settings.md @@ -0,0 +1,115 @@ +```json +{ + "useWhiteList": "boolean", + + "jdbc": { + "url": "String", + "username": "String", + "password": "String" + }, + + "server": { + "hostName": "String - url e.g. http://127.0.0.1", + "port": "int", + "grpcPort": "int", + "bindingAddress": "String - url with port e.g. http://127.0.0.1:9001", + "communicationType": "enum REST,GRPC", + + "sslConfig": { + "tls": "enum STRICT,OFF", + "generateKeyStoreIfNotExisted": "boolean", + "serverKeyStore": "Path", + "serverTlsKeyPath": "Path", + "serverTlsCertificatePath": "Path", + "serverKeyStorePassword": "String", + "serverTrustStore": "Path", + "serverTrustCertificates": [ + "Path..." + ], + "serverTrustStorePassword": "String", + "serverTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "clientKeyStore": "Path", + "clientTlsKeyPath": "Path", + "clientTlsCertificatePath": "Path", + "clientKeyStorePassword": "String", + "clientTrustStore": "Path", + "clientTrustCertificates": [ + "Path..." + ], + "clientTrustStorePassword": "String", + "clientTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "knownClientsFile": "Path", + "knownServersFile": "Path" + }, + + "influxConfig": { + "hostName": "String - url e.g. http://hostname", + "port": "int", + "pushIntervalInSecs": "int", + "dbName": "String" + } + }, + + "peer": [ + { + "url": "String - url e.g. http://127.0.0.1:9000/" + } + ], + + "keys": { + "passwords": [ + "String..." + ], + "passwordFile": "Path", + "azureKeyVaultConfig": { + "url": "Azure Key Vault url" + }, + "hashicorpKeyVaultConfig": { + "url": "Hashicorp Vault url", + "approlePath": "String (defaults to 'approle' if not set)", + "tlsKeyStorePath": "Path to jks key store", + "tlsTrustStorePath": "Path to jks trust store" + }, + + "keyData": [ + { + "config": { + "data": { + "aopts": { + "variant": "Enum : id,d or i", + "memory": "int", + "iterations": "int", + "parallelism": "int" + }, + "bytes": "String", + "snonce": "String", + "asalt": "String", + "sbox": "String", + "password": "String" + }, + "type": "Enum: argon2sbox or unlocked. If unlocked is defined then config data is required. " + }, + "privateKey": "String", + "privateKeyPath": "Path", + "azureVaultPrivateKeyId": "String", + "azureVaultPrivateKeyVersion": "String", + "publicKey": "String", + "publicKeyPath": "Path", + "azureVaultPublicKeyId": "String", + "azureVaultPublicKeyVersion": "String", + "hashicorpVaultSecretEngineName": "String", + "hashicorpVaultSecretName": "String", + "hashicorpVaultSecretVersion": "Integer (defaults to 0 (latest) if not set)", + "hashicorpVaultPrivateKeyId": "String", + "hashicorpVaultPublicKeyId": "String" + } + ] + }, + + "alwaysSendTo": [ + "String..." + ], + + "unixSocketFile": "Path" +} +``` diff --git a/docs/Privacy/Tessera/Configuration/Tessera v0.8 sample settings.md b/docs/Privacy/Tessera/Configuration/Tessera v0.8 sample settings.md new file mode 100644 index 000000000..2cc5eabf1 --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Tessera v0.8 sample settings.md @@ -0,0 +1,142 @@ +**Changes:** +- added modular server configurations + +--- + +**Sample** + +```json +{ + "useWhiteList": "boolean", + + "jdbc": { + "url": "String", + "username": "String", + "password": "String" + }, + + "serverConfigs": [ + { + "app": "ThirdParty", + "enabled": true, + "serverSocket": { + "type": "INET", + "port": 9081, + "hostName": "http://localhost" + }, + "bindingAddress": "String - url with port e.g. http://127.0.0.1:9081", + "communicationType": "REST" + }, + + { + "app": "Q2T", + "enabled": true, + "serverSocket": { + "type": "UNIX", + "path": "/tmp/tm.ipc" + }, + "communicationType": "UNIX_SOCKET" + }, + + { + "app": "P2P", + "enabled": true, + "serverSocket": { + "type": "INET", + "port": 9001, + "hostName": "http://localhost" + }, + "bindingAddress": "String - url with port e.g. http://127.0.0.1:9001", + "sslConfig": { + "tls": "enum STRICT,OFF", + "generateKeyStoreIfNotExisted": "boolean", + "serverKeyStore": "Path", + "serverTlsKeyPath": "Path", + "serverTlsCertificatePath": "Path", + "serverKeyStorePassword": "String", + "serverTrustStore": "Path", + "serverTrustCertificates": [ + "Path..." + ], + "serverTrustStorePassword": "String", + "serverTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "clientKeyStore": "Path", + "clientTlsKeyPath": "Path", + "clientTlsCertificatePath": "Path", + "clientKeyStorePassword": "String", + "clientTrustStore": "Path", + "clientTrustCertificates": [ + "Path..." + ], + "clientTrustStorePassword": "String", + "clientTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "knownClientsFile": "Path", + "knownServersFile": "Path" + }, + "communicationType": "REST" + } + ], + + "peer": [ + { + "url": "url e.g. http://127.0.0.1:9000/" + } + ], + + "keys": { + "passwords": [ + "String..." + ], + "passwordFile": "Path", + "azureKeyVaultConfig": { + "url": "Azure Key Vault url" + }, + "hashicorpKeyVaultConfig": { + "url": "Hashicorp Vault url", + "approlePath": "String (defaults to 'approle' if not set)", + "tlsKeyStorePath": "Path to jks key store", + "tlsTrustStorePath": "Path to jks trust store" + }, + + "keyData": [ + { + "config": { + "data": { + "aopts": { + "variant": "Enum : id,d or i", + "memory": "int", + "iterations": "int", + "parallelism": "int" + }, + "bytes": "String", + "snonce": "String", + "asalt": "String", + "sbox": "String", + "password": "String" + }, + "type": "Enum: argon2sbox or unlocked. If unlocked is defined then config data is required. " + }, + "privateKey": "String", + "privateKeyPath": "Path", + "azureVaultPrivateKeyId": "String", + "azureVaultPrivateKeyVersion": "String", + "publicKey": "String", + "publicKeyPath": "Path", + "azureVaultPublicKeyId": "String", + "azureVaultPublicKeyVersion": "String", + "hashicorpVaultSecretEngineName": "String", + "hashicorpVaultSecretName": "String", + "hashicorpVaultSecretVersion": "Integer (defaults to 0 (latest) if not set)", + "hashicorpVaultPrivateKeyId": "String", + "hashicorpVaultPublicKeyId": "String" + } + ] + }, + + "alwaysSendTo": [ + "String..." + ], + + "unixSocketFile": "Path" +} +``` diff --git a/docs/Privacy/Tessera/Configuration/Tessera v0.9 sample settings.md b/docs/Privacy/Tessera/Configuration/Tessera v0.9 sample settings.md new file mode 100644 index 000000000..5a06586d4 --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Tessera v0.9 sample settings.md @@ -0,0 +1,249 @@ +**Changes:** +- collapsed server socket definitions into a single property `serverAddress` + +e.g. +```json +"serverSocket": { + "type":"INET", + "port": 9001, + "hostName": "http://localhost" +}, +``` +becomes +``` +"serverAddress": "http://localhost:9001", +``` + + +--- + +**Sample** + +```json +{ + "useWhiteList": "boolean", + + "jdbc": { + "url": "String", + "username": "String", + "password": "String" + }, + + "serverConfigs": [ + { + "app": "ENCLAVE", // Defines us using a remote enclave, leave out if using built-in enclave + "enabled": true, + "serverAddress": "http://localhost:9081", //Where to find the remote enclave + "communicationType": "REST" + }, + { + "app": "ThirdParty", + "enabled": true, + "serverAddress": "http://localhost:9081", + "bindingAddress": "String - url with port e.g. http://127.0.0.1:9081", + "communicationType": "REST" + }, + + { + "app": "Q2T", + "enabled": true, + "serverAddress": "unix:/tmp/tm.ipc", + "communicationType": "REST" + }, + + { + "app": "P2P", + "enabled": true, + "serverAddress": "http://localhost:9001", + "bindingAddress": "String - url with port e.g. http://127.0.0.1:9001", + "sslConfig": { + "tls": "enum STRICT,OFF", + "generateKeyStoreIfNotExisted": "boolean", + "serverKeyStore": "Path", + "serverTlsKeyPath": "Path", + "serverTlsCertificatePath": "Path", + "serverKeyStorePassword": "String", + "serverTrustStore": "Path", + "serverTrustCertificates": [ + "Path..." + ], + "serverTrustStorePassword": "String", + "serverTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "clientKeyStore": "Path", + "clientTlsKeyPath": "Path", + "clientTlsCertificatePath": "Path", + "clientKeyStorePassword": "String", + "clientTrustStore": "Path", + "clientTrustCertificates": [ + "Path..." + ], + "clientTrustStorePassword": "String", + "clientTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "knownClientsFile": "Path", + "knownServersFile": "Path" + }, + "communicationType": "REST" + } + ], + + "peer": [ + { + "url": "url e.g. http://127.0.0.1:9000/" + } + ], + + "keys": { + "passwords": [ + "String..." + ], + "passwordFile": "Path", + "azureKeyVaultConfig": { + "url": "Azure Key Vault url" + }, + "hashicorpKeyVaultConfig": { + "url": "Hashicorp Vault url", + "approlePath": "String (defaults to 'approle' if not set)", + "tlsKeyStorePath": "Path to jks key store", + "tlsTrustStorePath": "Path to jks trust store" + }, + + "keyData": [ + { + "config": { + "data": { + "aopts": { + "variant": "Enum : id,d or i", + "memory": "int", + "iterations": "int", + "parallelism": "int" + }, + "bytes": "String", + "snonce": "String", + "asalt": "String", + "sbox": "String", + "password": "String" + }, + "type": "Enum: argon2sbox or unlocked. If unlocked is defined then config data is required. " + }, + "privateKey": "String", + "privateKeyPath": "Path", + "azureVaultPrivateKeyId": "String", + "azureVaultPrivateKeyVersion": "String", + "publicKey": "String", + "publicKeyPath": "Path", + "azureVaultPublicKeyId": "String", + "azureVaultPublicKeyVersion": "String", + "hashicorpVaultSecretEngineName": "String", + "hashicorpVaultSecretName": "String", + "hashicorpVaultSecretVersion": "Integer (defaults to 0 (latest) if not set)", + "hashicorpVaultPrivateKeyId": "String", + "hashicorpVaultPublicKeyId": "String" + } + ] + }, + + "alwaysSendTo": [ + "String..." + ], + + "unixSocketFile": "Path" +} +``` + +--- + +**Sample enclave settings** + +```json +{ + "serverConfigs": [ + { + "app": "ENCLAVE", + "enabled": true, + "serverAddress": "http://localhost:9001", + "bindingAddress": "String - url with port e.g. http://127.0.0.1:9001", + "sslConfig": { + "tls": "enum STRICT,OFF", + "generateKeyStoreIfNotExisted": "boolean", + "serverKeyStore": "Path", + "serverTlsKeyPath": "Path", + "serverTlsCertificatePath": "Path", + "serverKeyStorePassword": "String", + "serverTrustStore": "Path", + "serverTrustCertificates": [ + "Path..." + ], + "serverTrustStorePassword": "String", + "serverTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "clientKeyStore": "Path", + "clientTlsKeyPath": "Path", + "clientTlsCertificatePath": "Path", + "clientKeyStorePassword": "String", + "clientTrustStore": "Path", + "clientTrustCertificates": [ + "Path..." + ], + "clientTrustStorePassword": "String", + "clientTrustMode": "Enumeration: CA, TOFU, WHITELIST, CA_OR_TOFU, NONE", + "knownClientsFile": "Path", + "knownServersFile": "Path" + }, + "communicationType": "REST" + } + ], + + "keys": { + "passwords": [ + "String..." + ], + "passwordFile": "Path", + "azureKeyVaultConfig": { + "url": "Azure Key Vault url" + }, + "hashicorpKeyVaultConfig": { + "url": "Hashicorp Vault url", + "approlePath": "String (defaults to 'approle' if not set)", + "tlsKeyStorePath": "Path to jks key store", + "tlsTrustStorePath": "Path to jks trust store" + }, + + "keyData": [ + { + "config": { + "data": { + "aopts": { + "variant": "Enum : id,d or i", + "memory": "int", + "iterations": "int", + "parallelism": "int" + }, + "bytes": "String", + "snonce": "String", + "asalt": "String", + "sbox": "String", + "password": "String" + }, + "type": "Enum: argon2sbox or unlocked. If unlocked is defined then config data is required. " + }, + "privateKey": "String", + "privateKeyPath": "Path", + "azureVaultPrivateKeyId": "String", + "azureVaultPrivateKeyVersion": "String", + "publicKey": "String", + "publicKeyPath": "Path", + "azureVaultPublicKeyId": "String", + "azureVaultPublicKeyVersion": "String", + "hashicorpVaultSecretEngineName": "String", + "hashicorpVaultSecretName": "String", + "hashicorpVaultSecretVersion": "Integer (defaults to 0 (latest) if not set)", + "hashicorpVaultPrivateKeyId": "String", + "hashicorpVaultPublicKeyId": "String" + } + ] + }, + + "alwaysSendTo": [ + "String..." + ] +} +``` diff --git a/docs/Privacy/Tessera/Configuration/Using CLI to override config.md b/docs/Privacy/Tessera/Configuration/Using CLI to override config.md new file mode 100644 index 000000000..15d1be27d --- /dev/null +++ b/docs/Privacy/Tessera/Configuration/Using CLI to override config.md @@ -0,0 +1,47 @@ +CLI options can be used to add to, or override, configuration defined in a `configfile`. + +Standard Tessera CLI options are prefixed with a single hyphen (e.g. `-configfile `), whilst the config override options are prefixed with a double hyphen (e.g. `--alwaysSendTo `). Use `tessera help` to see a complete list of CLI options. + +If a config value is included in both the `configfile` and the CLI, then the CLI value will take precendence. The exceptions to this rule are the `--peer.url ` and `--alwaysSendTo ` options. Instead of overriding, these CLI options append to any peer or alwaysSendTo urls in the provided `configfile`. For example, if the following was provided in a `configfile`: +``` +{ + ... + "peer": [ + { + "url": "http://localhost:9001" + } + ], + alwaysSendTo:[ + "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE=" + ], + ... +} +``` +and Tessera was run with the following overrides: +``` +tessera -configfile path/to/file --peer.url http://localhost:9002 --peer.url http://localhost:9003 --alwaysSendTo /+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc= --alwaysSendTo UfNSeSGySeKg11DVNEnqrUtxYRVor4+CvluI8tVv62Y= +``` +then Tessera will be started with the following equivalent configuration: +``` +{ + ... + "peer": [ + { + "url": "http://localhost:9001" + }, + { + "url": "http://localhost:9002" + }, + { + "url": "http://localhost:9003" + } + ], + alwaysSendTo:[ + "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE=", + "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + "UfNSeSGySeKg11DVNEnqrUtxYRVor4+CvluI8tVv62Y=" + ], + ... +} +``` +As demonstrated in this example, in certain cases multiple values can be provided by repeating the CLI option. This is supported for the `peer.url`, `alwaysSendTo`, `server.sslConfig.serverTrustCertificates` and `server.sslConfig.clientTrustCertificates` options. diff --git a/docs/Privacy/Tessera/How Tessera Works.md b/docs/Privacy/Tessera/How Tessera Works.md new file mode 100644 index 000000000..35563bf67 --- /dev/null +++ b/docs/Privacy/Tessera/How Tessera Works.md @@ -0,0 +1,28 @@ +### Private Transaction Process Flow + +Below is a description of how Private Transactions are processed in Quorum: + +![Quorum Tessera Privacy Flow](https://github.com/jpmorganchase/tessera/raw/master/Tessera%20Privacy%20flow.jpeg) + +In this example, Party A and Party B are party to Transaction AB, whilst Party C is not. + +1. Party A sends a Transaction to their Quorum Node, specifying the Transaction payload and setting `privateFor` to be the public keys for Parties A and B +2. Party A's Quorum Node passes the Transaction on to its paired Transaction Manager, requesting for it to store the Transaction payload +3. Party A's Transaction Manager makes a call to its associated Enclave to validate the sender and encrypt the payload +4. Party A's Enclave checks the private key for Party A and, once validated, performs the Transaction conversion. This entails: + + 1. generating a symmetric key and a random Nonce + 1. encrypting the Transaction payload and Nonce with the symmetric key from i. + 1. calculating the SHA3-512 hash of the encrypted payload from ii. + 1. iterating through the list of Transaction recipients, in this case Parties A and B, and encrypting the symmetric key from i. with the recipient's public key (PGP encryption) + 1. returning the encrypted payload from step ii., the hash from step iii. and the encrypted keys (for each recipient) from step iv. to the Transaction Manager +5. Party A's Transaction manager then stores the encrypted payload (encrypted with the symmetric key) and encrypted symmetric key using the hash as the index, and then securely transfers (via HTTPS) the hash, encrypted payload, and encrypted symmetric key that has been encrypted with Party B's public key to Party B's Transaction Manager. Party B's Transaction Manager responds with an Ack/Nack response. Note that if Party A does not receive a response/receives a Nack from Party B then the Transaction will not be propagated to the network. It is a prerequisite for the recipients to store the communicated payload. +6. Once the data transmission to Party B's Transaction Manager has been successful, Party A's Transaction Manager returns the hash to the Quorum Node which then replaces the Transaction's original payload with that hash, and changes the transaction's `V` value to 37 or 38, which will indicate to other nodes that this hash represents a private transaction with an associated encrypted payload as opposed to a public transaction with nonsensical bytecode. +7. The Transaction is then propagated to the rest of the network using the standard Ethereum P2P Protocol. +8. A block containing Transaction AB is created and distributed to each Party on the network. +9. In processing the block, all Parties will attempt to process the Transaction. Each Quorum node will recognise a `V` value of 37 or 38, identifying the Transaction as one whose payload requires decrypting, and make a call to their local Transaction Manager to determine if they hold the Transaction (using the hash as the index to look up). +10. Since Party C does not hold the Transaction, it will receive a `NotARecipient` message and will skip the Transaction - it will not update its Private StateDB. Party A and B will look up the hash in their local Transaction Managers and identify that they do hold the Transaction. Each will then make a call to its Enclave, passing in the Encrypted Payload, Encrypted symmetric key and Signature. +11. The Enclave validates the signature and then decrypts the symmetric key using the Party's private key that is held in The Enclave, decrypts the Transaction Payload using the now-revealed symmetric key and returns the decrypted payload to the Transaction Manager. +12. The Transaction Managers for Parties A and B then send the decrypted payload to the EVM for contract code execution. This execution will update the state in the Quorum Node's Private StateDB only. NOTE: once the code has been executed it is discarded so is never available for reading without going through the above process. + + diff --git a/docs/Privacy/Tessera/Migration from Constellation.md b/docs/Privacy/Tessera/Migration from Constellation.md new file mode 100644 index 000000000..c76234cd9 --- /dev/null +++ b/docs/Privacy/Tessera/Migration from Constellation.md @@ -0,0 +1,125 @@ +## Migration Utilities +Two utilities are included to help with migrating existing Constellation configurations and datastores for use with Tessera. These utilites are included in the Tessera project and are available for use after building Tessera with Maven. + +A full migration workflow would be as follows: + +1. Shut down the Constellation/Quorum nodes +2. Perform [database migration](#data-migration) +3. Perform [configuration migration](#configuration-migration) +4. Start Tessera/Quorum nodes + + +## Data Migration +This utility migrates a Constellation datastore (BerkeleyDB or directory/file storage) to a Tessera compatible one (H2, SQLITE). By default Tessera uses an H2 database, however alternatives can be configured. Refer [DDLs](https://github.com/jpmorganchase/tessera/tree/master/ddls/create-table) for help with defining with other databases. + +To make running the utility commands simpler, you can first create an `alias`: + +``` +alias tessera-data-migration="java -jar /path/to/tessera/data-migration/target/data-migration-${version}-cli.jar" +``` + +CLI help can be accessed by running: +``` +tessera-data-migration help + +usage: tessera-data-migration +-exporttype Export DB type i.e. h2, sqlite +-inputpath Path to input file or directory +-outputfile Path to output file +-storetype Store type i.e. bdb, dir +-dbuser Set a username on the migrated database (only applies to H2) +-dbpass Set a password for the specified user (only applies to H2) +``` + +#### Migrating BerkeleyDB (bdb) +To migrate a BerkeleyDB (bdb) database for use with Tessera you must first export your existing store using `db_dump`: +``` +db_dump -f exported.txt c1/cn§.db/payload.db +``` + +Then run the following command to perform the migration: +``` +tessera-data-migration -storetype bdb -inputpath exported.txt -dbuser -dbpass -outputfile -exporttype +``` + +#### Migrating Directory/File (dir) storage +For dir storage: +``` +tessera-data-migration -storetype dir -inputpath /path/to/dir -dbuser -dbpass -outputfile -exporttype +``` + +### Output types +To use H2 as the output storage, specify: +``` +-exporttype h2 -outputfile /path/to/h2database +``` + +To use SQLite as the output storage, specify: +``` +-exporttype sqlite -outputfile /path/to/sqlitedb +``` + +#### Database usernames and passwords +If you want to set a username and password on the migrated database, you must specify this using the following options: + +``` +-dbuser -dbpass +``` + +If you do not wish to set a username and password on the migrated database, you must explicitly say so by specifying the arguments without parameters, i.e. + +``` +-dbuser -dbpass +``` + +Note also that even though SQLite does not have the concept of usernames and passwords, you must still specify at least the empty configuration. + + +#### After migration +The output file should then be placed in a location of your choosing that corresponds to the location specified in the configuration file (without any file extension), i.e. + +``` +"jdbc": { + "url": "jdbc:h2:./c1/migratedfile;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0" +} +``` + +Note: the migrated database is migrated without user credentials, so if using the file directly then the username and password should not be specified in the configuration. + +The Constellation files are no longer used, and can be cleaned up or left alone. + + +## Configuration Migration +This utility will generate a Tessera compatible `.json` format configuration file from an existing Constellation `.toml` configuration file. The `.json` file will be saved locally to be used when running Tessera. Individual configuration parameters can be overridden during the migration process if required. + +To make running the utility commands simpler, you can first create an `alias`: + +``` +alias tessera-config-migration="java -jar /path/to/tessera/config-migration/target/config-migration-${version}-cli.jar" +``` + +Most of the Constellation configuration command line parameters are supported. For details of the Constellation configuration see the [Constellation documentation](../../Constellation/Constellation). + +To see the CLI help which provides details on overriding specific configuration items from a `.toml` file, run: +``` +tessera-config-migration help +``` + +To migrate a `.toml` file to `.json` with no overrides, run: +``` +tessera-config-migration --tomlfile="/path/to/constellation-config.toml" +``` + +By default, the generated `.json` config will be printed to the console and saved to `./tessera-config.json`. To save to another location/with a different filename use the `--outputfile ` CLI option. + +#### Note about `ipwhitelist` +Unlike Constellation, Tessera does not use a separate `ipwhitelist`. If `useWhiteList` is set to `true` in the `.json` config then the `peers` list will be used as the whitelist. + +If `ipwhitelist` is provided in an existing `.toml` config file then this will only be used to set `useWhiteList: true`; any nodes included in this list will not be added by default to the Tessera config. Make sure to add any nodes that were only included in `ipwhitelist` to `peers` after using the utility. + +#### Validation +Validation is applied to the generated config. Messages will be printed to the terminal if the validation identifies issues. For example, if a `hostname` is not provided then the following message will be printed: +``` +Warning: may not be null on property serverConfig.hostName +``` +Any validation violations will have to be addressed before the config can be used with Tessera. diff --git a/docs/Privacy/Tessera/Tessera Services/Enclave.md b/docs/Privacy/Tessera/Tessera Services/Enclave.md new file mode 100644 index 000000000..a8dc9c44e --- /dev/null +++ b/docs/Privacy/Tessera/Tessera Services/Enclave.md @@ -0,0 +1,124 @@ +## Enclave + +### What is an enclave? + +An enclave is a secure processing environment that acts as a black box for processing commands and data. Enclaves come in various forms, some on hardware and others in software. In all scenarios, the purpose is to protect information that exists inside of the enclave from malicious attack. + +### What does a Tessera enclave do? + +The Tessera enclave is designed to handle all of the encryption/decryption operations required by the Transaction Manager, as well as all forms of key management. + +This enables all sensitive operations to be handled in a single place, without any leakage into areas of program memory that don't need access. This also means that a smaller application can be run in a secure environment, where memory constraints are often more stringent, such as hardware enclaves. + +The Transaction Manager, which handles peer management and database access, as well as Quorum communication does not perform **any** encryption/decryption, greatly reducing the impact an attack can have. + +### What exactly does the enclave handle? + +The Tessera enclave **handles** the following data: + +- public/private key access +- public keys of extra recipients (** should be moved into Transaction Manager, not sensitive) +- default identity of attached nodes + +The enclaves **performs** the following actions on request: + +- fetching the default identity for attached nodes (default public key) +- providing forwarding keys for all transactions (** should be moved into Transaction Manager, not sensitive) +- returning all public keys managed by this enclave +- encrypting a payload for given sender and recipients +- encrypting raw payloads for given sender +- decrypting transactions for a given recipient (or sender) +- adding new recipients for existing payloads + +### Where does the Enclave sit in the private transaction flow? + +The Enclave is the innermost actor of the sequence of events. The below diagram demonstrates where the enclave sits: + +![Quorum Tessera Privacy Flow](https://github.com/jpmorganchase/tessera/raw/master/Tessera%20Privacy%20flow.jpeg) + +As the diagram shows, each enclave interacts only with it's own transaction manager and no-one else. + +Tessera provides different types of Enclaves to suit different needs: + +### Types of Enclave + +#### Local enclave +The local enclave is the classical option that was included in versions of Tessera prior to v0.9. This includes the enclave inside the same process and the transaction manager. This is still an option, and requires all the enclave configuration to be inside the same configuration file and the Transaction Manager configuration. + +##### How to use? +In order to use the local enclave, you simply need to not specify an Enclave server type in the configuration. don't forget to specify the enclave config in the Transaction Manager config file. + + +#### HTTP Enclave +The HTTP Enclave is a remote enclave that serves RESTful endpoints over HTTP. This allows a clear separation of concerns for between the Enclave process and Transaction Manager (TM) process. The enclave must be present and running at TM startup as it will be called upon for initialisation. + +##### How to use? +The HTTP enclave can be started up by specifying an `ENCLAVE` server app type, with REST as the communication type. This same configuration should be put into the TM configuration so it knows where to find the remote enclave. Remember to set TLS settings as appropriate, with the TM being a client of the Enclave. + +##### Advantage? +The HTTP enclave could be deployed in a completely secure environment away from local machine where TM process runs and it adds this additional layer of security for private keys which is only accessible from HTTP enclave. + + +### Setting up an Enclave + +### Configuration + +The configuration for the enclave is designed to the same as for the Transaction Manager. + +#### Local Enclave Setup +The following should be present in the TM configuration: +```json +{ + "keys": { + "keyData": [{ + "privateKey": "yAWAJjwPqUtNVlqGjSrBmr1/iIkghuOh1803Yzx9jLM=", + "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + }] + }, + + "alwaysSendTo": [] +} +``` + +#### Remote Enclave Setup +The configuration required is minimal, and only requires the following from the main config (as an example): + +In the remote enclave config: +```json +{ + "serverConfigs": [{ + "app": "ENCLAVE", + "enabled": true, + "serverAddress": "http://localhost:8080", + "communicationType": "REST", + "bindingAddress": "http://0.0.0.0:8080" + }], + + "keys": { + "keyData": [{ + "privateKey": "yAWAJjwPqUtNVlqGjSrBmr1/iIkghuOh1803Yzx9jLM=", + "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + }] + }, + + "alwaysSendTo": [] +} +``` + +and in the TM configuration: +```json +"serverConfigs": [{ + "app": "ENCLAVE", + "enabled": true, + "serverAddress": "http://localhost:8080", + "communicationType": "REST" +}], +``` +The keys are the same as the Transaction Manager configuration, and can use all the key types including vaults. When using a vault with the enclave, be sure to include the corresponding jar on the classpath, either: + +* `/path/to/azure-key-vault-0.9-SNAPSHOT-all.jar` +* `/path/to/hashicorp-key-vault-0.9-SNAPSHOT-all.jar` + +If using the all-in-one Transaction Manager jar, all the relevant files are included, and just the configuration needs to be updated for the TM. + +If using the individual "make-your-own" jars, you will need the "core Transaction Manager" jar along with the "Enclave clients" jar, and add them both to the classpath as such: `java -cp /path/to/transactionmanager.jar:/path/to/enclave-client.jar com.quroum.tessera.Launcher -configfile /path/to/config.json` diff --git a/docs/Privacy/Tessera/Tessera Services/Keys/Keys.md b/docs/Privacy/Tessera/Tessera Services/Keys/Keys.md new file mode 100644 index 000000000..d3611a715 --- /dev/null +++ b/docs/Privacy/Tessera/Tessera Services/Keys/Keys.md @@ -0,0 +1,114 @@ +## Generating keys + +Key generation can be used in multiple ways: + +1. Generate a key pair and save in new files `.pub` and `.key`: + ``` + tessera -keygen + ``` + This command will require interactive input for passwords. +If you wish to generate an unlocked key, `/dev/null` can be used for stdin to tell the application not to expect any input (version 0.8 only): + ``` + # Version 0.8+ + tessera -keygen < /dev/null + + # Version 0.7.x or before + printf "\n\n" | tessera -keygen + ``` + + The `-filename` option can be used to specify alternate filepaths. Multiple key pairs can be generated at the same time by providing a comma-separated list of values: + ``` + tessera -keygen -filename /path/to/key1,/path/to/key2 + ``` + +1. Generate a key pair and save to an Azure Key Vault, with DNS name ``, as secrets with IDs `Pub` and `Key`: + ``` + tessera -keygen -keygenvaulttype AZURE -keygenvaulturl + ``` + + The `-filename` option can be used to specify alternate IDs. Multiple key pairs can be generated at the same time by providing a comma-separated list of values: + ``` + tessera -keygen -keygenvaulttype AZURE -keygenvaulturl -filename id1,id2 + ``` + + **Note: If saving new keys with the same ID as keys that already exist in the vault, the existing keys will be replaced by the newer version.** + + > Environment variables must be set if using an Azure Key Vault, for more information see [Setting up an Azure key vault](../Setting%20up%20an%20Azure%20Key%20Vault) + +1. Generate a key pair and save to a Hashicorp Vault at the secret path `secretEngine/secretName` with IDs `publicKey` and `privateKey`: + ```bash + tessera -keygen -keygenvaulttype HASHICORP -keygenvaulturl \ + -keygenvaultsecretengine secretEngine -filename secretName + ``` + Options exist for configuring TLS and AppRole authentication (by default the AppRole path is set to `approle`): + ```bash + tessera -keygen -keygenvaulttype HASHICORP -keygenvaulturl \ + -keygenvaultsecretengine -filename \ + -keygenvaultkeystore -keygenvaulttruststore \ + -keygenvaultapprole + ``` + The `-filename` option can be used to generate and store multiple key pairs at the same time: + ```bash + tessera -keygen -keygenvaulttype HASHICORP -keygenvaulturl \ + -keygenvaultsecretengine secretEngine -filename myNode/keypairA,myNode/keypairB + ``` + **Saving a new key pair to an existing secret will overwrite the values stored at that secret. Previous versions of secrets may be retained and be retrievable by Tessera depending on how the K/V secrets engine is configured. See [Keys](../../../Configuration/Keys) for more information on configuring Tessera for use with Vault.** + + > Environment variables must be set if using a Hashicorp Vault, and a version 2 K/V secret engine must be enabled. For more information see [Setting up a Hashicorp Vault](../Setting%20up%20a%20Hashicorp%20Vault). + +1. Generate a key pair, save to files and then start Tessera using a provided config + ``` + tessera -keygen -configfile /path/to/config.json + ``` + ``` + tessera -keygen -filename key1 -configfile /path/to/config.json + ``` + Tessera loads `config.json` as usual and includes the newly generated key data before starting. + + An updated `.json` configfile is printed to the terminal (or to a file if using the `-output` CLI option). No changes are made to the `config.json` file itself. + +## Securing private keys +Generated private keys can be encrypted with a password. This is prompted for on the console during key generation. After generating password-protected keys, the password must be added to your configuration to ensure Tessera can read the keys. The password is not saved anywhere but must be added to the configuration else the key will not be able to be decrypted. + +Passwords can be added to the json config either inline using `"passwords":[]`, or stored in an external file that is referenced by `"passwordFile": "Path"`. Note that the number of arguments/file-lines provided must equal the total number of private keys. For example, if there are 3 total keys and the second is not password secured, the 2nd argument/line must be blank or contain dummy data. + +Tessera uses Argon2 in the process of encrypting private keys. By default, Argon2 is configured as follows: +``` +{ + "variant": "id", + "memory": 1048576, + "iterations": 10, + "parallelism": 4 +} +``` +The Argon2 configuration can be altered by using the `-keygenconfig` option. Any override file must have the same format as the default configuration above and all options must be provided. +``` +tessera -keygen -filename /path/to/key1 -keygenconfig /path/to/argonoptions.json +``` + +For more information on Argon2 see the [Argon2 Github page](https://github.com/P-H-C/phc-winner-argon2). + +### Updating password protected private keys +The password of a private key stored in a file can be updated. Password update uses the `--keys.keyData.privateKeyPath` CLI option to get the path to the file. + +Password update can be used in multiple ways. Running any of these commands will start a CLI prompt to allow you to set a new password. + +1. Add a password to an unlocked key + ``` + tessera -updatepassword --keys.keyData.privateKeyPath /path/to/.key + ``` + +1. Change the password of a locked key. This requires providing the current password for the key (either inline or as a file) + ``` + tessera -updatepassword --keys.keyData.privateKeyPath /path/to/.key --keys.passwords + ``` + or + ``` + tessera -updatepassword --keys.keyData.privateKeyPath /path/to/.key --keys.passwordFile /path/to/pwds + ``` + +1. Use different Argon2 options from the defaults when updating the password + ``` + tessera --keys.keyData.privateKeyPath --keys.keyData.config.data.aopts.algorithm --keys.keyData.config.data.aopts.iterations --keys.keyData.config.data.aopts.memory --keys.keyData.config.data.aopts.parallelism + ``` + All options have been overriden here but only the options you wish to alter from their defaults need to be provided. diff --git a/docs/Privacy/Tessera/Tessera Services/Keys/Setting up a Hashicorp Vault.md b/docs/Privacy/Tessera/Tessera Services/Keys/Setting up a Hashicorp Vault.md new file mode 100644 index 000000000..72f78fcac --- /dev/null +++ b/docs/Privacy/Tessera/Tessera Services/Keys/Setting up a Hashicorp Vault.md @@ -0,0 +1,56 @@ +The private/public key pairs used by Tessera can be [stored](../Keys) in and [retrieved](../../../Configuration/Keys) from a key vault, preventing the need to store the keys locally. + +This page details how to set up and configure a Hashicorp Vault for use with Tessera. + + The [Hashicorp Vault Getting Started documentation](https://learn.hashicorp.com/vault/) provides much of the information needed to get started. The following section goes over some additional considerations when running Tessera with Vault. + +## Configuring the vault + +### TLS +When running in production situations it is advised to configure the Vault server for 2-way (mutual) TLS communication. Tessera also supports 1-way TLS and unsecured (no TLS) communications with a Vault server. + +An example configuration for the Vault listener to use 2-way TLS is shown below. This can be included as part of the `.hcl` used when starting the Vault server: + +``` +listener "tcp" { + tls_min_version = "tls12" + tls_cert_file = "/path/to/server.crt" + tls_key_file = "/path/to/server.key" + tls_require_and_verify_client_cert = "true" + tls_client_ca_file = "/path/to/client-ca.crt" +} +``` + +### Auth methods +Tessera directly supports the [AppRole](https://www.vaultproject.io/docs/auth/approle.html) auth method. If required, other auth methods can be used by logging in outside of Tessera (e.g. using the HTTP API) and providing the resulting vault token to Tessera. See the *Enabling Tessera to use the vault* section below for more information. + +When using AppRole, Tessera assumes the default auth path to be `approle`, however this value can be overwritten. See [Keys](../../../Configuration/Keys) for more information. + +### Policies +To be able to carry out all possible interactions with a Vault, Tessera requires the following policy capabilities: `["create", "update", "read"]`. A subset of these capabilities can be configured if not all functionality is required. + +### Secret engines +Tessera can read and write keys to the following secret engine type: + +- [K/V Version 2](https://www.vaultproject.io/docs/secrets/kv/kv-v2.html) + +The K/V Version 2 secret engine supports versioning of secrets, however only a limited number of versions are retained. This number can be changed as part of the Vault configuration process. + +## Enabling Tessera to use the vault +### Environment Variables +If using a Hashicorp Vault, Tessera requires certain environment variables to be set depending on the auth method being used. + +- If using the AppRole auth method, set: + 1. `HASHICORP_ROLE_ID` + 2. `HASHICORP_SECRET_ID` + + These credentials are obtained as outlined in the [AppRole documentation](https://www.vaultproject.io/docs/auth/approle.html). Tessera will use these credentials to authenticate with Vault. + +- If using the root token or you already have a token due to authorising with an alternative method, set: + 1. `HASHICORP_TOKEN` + +!!! note + If using TLS additional environment variables must be set. See [Keys](../../../Configuration/Keys) for more information as well as details of the Tessera configuration required to retrieve keys from a Vault. + +### Dependencies +The Hashicorp dependencies are included in the `tessera-app--app.jar`. If using the `tessera-simple--app.jar` then `hashicorp-key-vault--all.jar` must be added to the classpath. diff --git a/docs/Privacy/Tessera/Tessera Services/Keys/Setting up an Azure Key Vault.md b/docs/Privacy/Tessera/Tessera Services/Keys/Setting up an Azure Key Vault.md new file mode 100644 index 000000000..a7d3503bf --- /dev/null +++ b/docs/Privacy/Tessera/Tessera Services/Keys/Setting up an Azure Key Vault.md @@ -0,0 +1,72 @@ + +The private/public key pairs used by Tessera can be [stored](../Keys) in and [retrieved](../../../Configuration/Keys) from a key vault, preventing the need to store the keys locally. + +This page details how to set up and configure an Azure Key Vault for use with Tessera. + +The Microsoft Azure documentation provides much of the information needed to get started. The information in this section has been taken from the following pages of the Azure documentation: + +* https://docs.microsoft.com/en-us/azure/key-vault/quick-create-node +* https://docs.microsoft.com/en-us/azure/key-vault/key-vault-get-started + +## Creating the vault +The Key Vault can be created using either the [Azure Web Portal](https://azure.microsoft.com/en-gb/features/azure-portal/) or the [Azure CLI](https://docs.microsoft.com/en-gb/cli/azure/install-azure-cli?view=azure-cli-latest). +### Using the portal +1. Login to the Azure Portal +1. Select `Create a resource` from the sidebar +1. Search for, and select, `Key Vault` +1. Fill out the necessary fields, including choosing a suitable name and location (the list of possible locations can be found using the Azure CLI, see below), and click `Create` + +### Using the CLI +1. Login to Azure using the [Azure CLI](https://docs.microsoft.com/en-gb/cli/azure/install-azure-cli?view=azure-cli-latest) + ``` + az login + ``` + +1. Create a resource group, choosing a suitable name and location + + ``` + az group create --name --location + ``` + + To view a list of possible locations use the command + + ``` + az account list-locations + ``` + +1. Create the Key Vault, choosing a suitable name and location and referencing the resource group created in the previous step + ``` + az keyvault create --name --resource-group --location + ``` +A Key Vault has now been created that can be used to store secrets. + +## Configuring the vault to work with Tessera +Azure uses an Active Directory system to grant access to services. We will create an 'application' that we will authorise to use the vault. We will provide the credentials created as a result of this to authenticate our Tessera instance to use the key vault. + +In order for the vault to be accessible by Tessera, the following steps must be carried out: + +1. Log in to the Azure Portal +1. Select `Azure Active Directory` from the sidebar +1. Select `App registrations`, `New application registration` and complete the registration process. **Make note of the `Application ID`**. +1. Once registered, click `Settings`, `Keys`, and create a new key with a suitable name and expiration rule. **Once the key has been saved make note of the key value - this is the only opportunity to see this value!** + +To authorise the newly registered app to use the Key Vault complete the following steps: + +1. Select `All services` from the sidebar and select `Key vaults` +1. Select the vault +1. Select `Access policies` and `Add new` +1. Search for and select the newly registered application as the `Principal` +1. Enable the `Get` and `Set` secret permissions + +## Enabling Tessera to use the vault +### Environment Variables + +If using an Azure Key Vault, Tessera requires two environment variables to be set: + +1. `AZURE_CLIENT_ID`: The `Application ID` +1. `AZURE_CLIENT_SECRET`: The application registration `key` + +Both of these values can be retrieved during the application registration process as outlined above. + +### Dependencies +The Azure dependencies are included in the `tessera-app--app.jar`. If using the `tessera-simple--app.jar` then `azure-key-vault--all.jar` must be added to the classpath. diff --git a/docs/Privacy/Tessera/Tessera Services/Transaction Manager.md b/docs/Privacy/Tessera/Tessera Services/Transaction Manager.md new file mode 100644 index 000000000..1e1f31b56 --- /dev/null +++ b/docs/Privacy/Tessera/Tessera Services/Transaction Manager.md @@ -0,0 +1,74 @@ +## Transaction Manager + +### What is a transaction manager? + +A transaction manager is the central piece in the lifecycle of a private transaction. It interfaces with most other parts of the network/infrastructure and manages the lifecycle of private data. + +### What does a transaction manager do? + +The transaction manager's duties include: + +- forming a P2P network of transaction managers & broadcasting peer/key information +- interfacing with the enclave for encrypting/decrypting private payloads +- storing and retrieving saved data from the database +- providing the gateway for Quorum to distribute private information + +The Transaction Manager, which handles peer management and database access, as well as Quorum communication, does not contain access to any private keys and does not perform and encryption/decryption, greatly reducing the impact an attack can have. + +### Where does the transaction manager sit in the private transaction flow? + +The transaction manager is the touch point for Quorum to distribute it's private payloads. It connects directly to Quorum and interfaces with the attached enclave, as well as with other transaction managers. + +![Quorum Tessera Privacy Flow](https://github.com/jpmorganchase/tessera/raw/master/Tessera%20Privacy%20flow.jpeg) + +## Setting up a Transaction Manager + +### Running Tessera +The only mandatory parameter for running a minimal Transaction Manager is the location of the configuration file to use. +Use the `-configfile ` argument to specify the location of the config file. + +Other CLI arguments can be passed, and details of these commands can be found in their respective pages - particularly around key vaults and key generation. + +### Databases +By default, Tessera uses an H2 file-based database, but any JDBC compatible database can be used. + +To do this, add the necessary drivers to the classpath, and run the `com.quorum.tessera.Launcher` class, like the following: + +``` +java -cp some-jdbc-driver.jar:/path/to/tessera-app.jar:. com.quorum.tessera.Launcher +``` + +For example, to use Oracle database: +``` +java -cp ojdbc7.jar:tessera-app.jar:. com.quorum.tessera.Launcher -configfile config.json +``` + +Some DDL scripts have been provided for more popular databases, but feel free to adapt these to whichever database you wish to use. + +### Configuration + +The configuration for the transaction manager is described in the [configuration overview](../../Configuration/Configuration Overview), as well as [sample configurations](../../Configuration/Sample Configuration). + +### Flavours of transaction manager +For advanced users, you may decide on certain options for the transaction manager, or to disable other parts. + +The default transaction manager comes with the standard options most setups will use, but other versions are as follows: + +- GRPC communication (experimental) +- Non-remote only enclaves (named "tessera-simple") + +These must be built from source and can be found inside the `tessera-dist` module. + + +## Data recovery + +Tessera contains functionality to request transactions from other nodes in the network; this is useful if the database is lost or corrupted somehow. +However, depending on the size of the network and the number of transactions made between peers, this can put heavy strain on the network resending all the data. + +### How to enable +The data recovery mechanism is intended to be a "switch-on" feature as a startup command. The times when you will need this will be known prior to starting the application (usually after a disaster event). When starting Tessera, simply add the following property to the startup command: `-Dspring.profiles.active=enable-sync-poller`. This should go before any jar or class definitions, e.g. `java -Dspring.profiles.active=enable-sync-poller -jar tessera.jar -configfile config.json`. + +### How it works +The data recovery procedure works by invoking a "resend request" to each new node it sees in the network. This request will cause the target node to resend each of its transactions to the intended recipient, meaning they will again save the transaction in their database. + +The target node will not send back transactions as a response the request in order to ensure that a malicious node cannot get access to the transactions. i.e. anyone can send a request for a particular key, but it will mean that the node that holds that key will receive the transactions, not the node making the request. In normal usage, the node making the request and the node holding the public key are the same. diff --git a/docs/Privacy/Tessera/Tessera.md b/docs/Privacy/Tessera/Tessera.md new file mode 100644 index 000000000..680e6317a --- /dev/null +++ b/docs/Privacy/Tessera/Tessera.md @@ -0,0 +1,20 @@ +## Tessera + +Tessera is a stateless Java system that is used to enable the encryption, decryption, and distribution of private transactions for [Quorum](/). + +Each Tessera node: + +* Generates and maintains a number of private/public key pairs + +* Self manages and discovers all nodes in the network (i.e. their public keys) by connecting to as few as one other node + +* Provides Private and Public API interfaces for communication: + * Private API - This is used for communication with Quorum + * Public API - This is used for communication between Tessera peer nodes + +* Provides two way SSL using TLS certificates and various trust models like Trust On First Use (TOFU), whitelist, + certificate authority, etc. + +* Supports IP whitelist + +* Connects to any SQL DB which supports the JDBC client diff --git a/docs/Privacy/Tessera/Usage/Admin Usage.md b/docs/Privacy/Tessera/Usage/Admin Usage.md new file mode 100644 index 000000000..721a23a9d --- /dev/null +++ b/docs/Privacy/Tessera/Usage/Admin Usage.md @@ -0,0 +1,19 @@ +Administrators of a Tessera node can use the `admin` CLI command to make changes to the node. These changes are made while the node is running and do not require a node restart. + +The `admin` CLI makes use of the [ADMIN server API](../Interface%20%26%20API) and provides some additional features. An ADMIN server must have been configured at startup (see [Configuration Overview](../../Configuration/Configuration%20Overview)). + +After starting a node with `tessera -configfile /path/to/node-config.json`, the admin CLI can be used. Currently supported admin commands are: +- `addpeer`: Add a new peer to a running node + +### `addpeer` +``` +tessera admin -configfile /path/to/node-config.json -addpeer +``` +The provided configfile is the same configfile used to start the Tessera node. + +This will do two things: + +1. Add `` to the node's list of peers, by using the ADMIN API +1. Update the configfile `/path/to/node-config.json` to include `` in the `peer` list. Updating the configfile in this way means that if the node is stopped and started again, the admin changes will still be present. + +If the configfile should not be updated, use the ADMIN API directly. diff --git a/docs/Privacy/Tessera/Usage/Interface & API.md b/docs/Privacy/Tessera/Usage/Interface & API.md new file mode 100644 index 000000000..276a49798 --- /dev/null +++ b/docs/Privacy/Tessera/Usage/Interface & API.md @@ -0,0 +1,120 @@ +## Interface Details + +All interfaces can be set to run over HTTP, GRPC or HTTP-over-Unix-Sockets. + +### gRPC (for inter-node communication) + +We currently have an implementation of gRPC for peer node communication as experiment API. This is not enabled on Quorum yet, but between Tessera nodes they can be enabled by adding in a couple of properties in the configuration file as child elements of `serverConfig`. + +- `grpcPort` - when this value is specified, Tessera node will start a gRPC server listening on this port. The normal `port` value would still be used for starting REST server. + +- `communicationType` - possible values are `REST`, `GRPC`. Default value is `REST`. + +Please note that communication between Quorum and Tessera are still via unix socket. This communication flag provides additional options for Tessera peer-to-peer communication. If gRPC is the option specified, please ensure the peers urls are provided with the appropriate ports. + +--- + +### Tessera to Tessera - Public API + +Tessera nodes communicate with each other for: + +- Node/network discovery +- Sending/Receiving encrypted payloads + +The following endpoints are advertised on this interface: + +* `/version` +* `/upcheck` +* `/push` +* `/resend` +* `/partyinfo` + +### Third Party - Public API + +Tessera nodes communicate with third parties for: + +- storing encrypted payloads for external applications + +The following endpoints are advertised on this interface: + +* `/version` +* `/upcheck` +* `/storeraw` + +### Quorum to Tessera - Private API + +Quorum uses this API to: +- Check if the local Tessera node is running +- Send and receive details of private transactions + +The following endpoints are advertised on this interface: +- `/version` +- `/upcheck` +- `/sendraw` +- `/send` +- `/receiveraw` +- `/receive` +- `/sendsignedtx` + +### Admin API + +Admins should use this API to: +- Access information about the Tessera node +- Make changes to the configuration of the Tessera node + +The following endpoints are advertised on this API: +- `/peers` - Add to, and retrieve from, the Tessera node's peers list +- `/keypairs` - Retrieve all public keys or search for a particular public key in use by the Tessera node + +## API Details + +**`version`** - _Get Tessera version_ + +- Returns the version of Tessera that is running. + +**`upcheck`** - _Check Tessera node is running_ + +- Returns the text "I'm up!" + +**`push`** - _Push transactions between nodes_ + +- Persist encrypted payload received from another node. + +**`resend`** - _Resend transaction_ + +- Resend all transactions for given key or given hash/recipient. + +**`partyinfo`** - _Retrieve details of known nodes_ + +- GET: Request public keys/url of all known peer nodes. +- POST: accepts a stream that contains the caller node's network information, and returns a merged copy with the callee node's network information + +**`sendraw`** - _Send transaction bytestring_ + +- Send transaction payload bytestring from Quorum to Tessera node. Tessera sends the transaction hash in the response back. + +**`send`** - _Send transaction bytestring_ + +- Similar to sendraw however request payload is in json format. Please see our [Swagger documentation](https://jpmorganchase.github.io/tessera-swagger/index.html) for object model. + +**`storeraw`** - _Store transaction bytestring_ + +- Store transaction bytestring from a third party to Tessera node. Tessera sends the transaction hash in the response back. + +**`sendsignedtx`** - _Distribute signed transaction payload_ + +- Send transaction payload identified by hash (returned by storeraw) from Quorum to Tessera node. Tessera sends the transaction hash in the response back. + +**`receiveraw`** - _Receive transaction bytestring_ + +- Receive decrypted bytestring of the transaction payload from Tessera to Quorum for transactions it is party to. + +**`receive`** - _Receive transaction bytestring_ + +- Similar to receiveraw however request payload is in json format. Please see our [Swagger documentation](https://jpmorganchase.github.io/tessera-swagger/index.html) for object model. + +**`delete`** - _Delete a transaction_ + +- Delete hashed encrypted payload stored in Tessera nodes. + +For more interactions with the API see the [Swagger documentation](https://jpmorganchase.github.io/tessera-swagger/index.html). diff --git a/docs/Privacy/Tessera/Usage/Monitoring.md b/docs/Privacy/Tessera/Usage/Monitoring.md new file mode 100644 index 000000000..25fb5c783 --- /dev/null +++ b/docs/Privacy/Tessera/Usage/Monitoring.md @@ -0,0 +1,82 @@ +## Using Splunk +Tessera logs can be interpreted by Splunk to allow for monitoring and analysis. The general steps to set up Splunk monitoring for a network of Tessera nodes are: + +1. If one does not already exist, set up a central Splunk instance (a Receiver) on a separate host. +1. Configure the Tessera hosts to forward their logging info to the Receiver by: + 1. Providing Logback configuration to Tessera as a CLI arg on start-up to specify the format of the logging output (e.g. save to a file). + This is achieved by providing an XML/Groovy config file defining the logging-level and Logback Appenders to use, for example: + ``` xml + + + + /path/to/file.log + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + ``` + + Further information can be found in the [Logback documentation](https://logback.qos.ch/manual/configuration.html#syntax). + + 1. Set up Splunk Universal Forwarders on each Tessera host. These are lightweight Splunk clients that will be used to collect and pass logging data to the central Splunk instance for analysis. + 1. Set up the central Splunk instance to listen and receive logging data from the Universal Forwarders + + +Further information about setting up Splunk and Universal Forwarders can be found in the Splunk documentation. The following pages are a good starting point: + +* [Consolidate data from multiple hosts](http://docs.splunk.com/Documentation/Forwarder/7.1.2/Forwarder/Consolidatedatafrommultiplehosts) +* [Set up the Universal Forwarder](http://docs.splunk.com/Documentation/Splunk/7.1.2/Forwarding/EnableforwardingonaSplunkEnterpriseinstance#Set_up_the_universal_forwarder) +* [Configure the Universal Forwarder](http://docs.splunk.com/Documentation/Forwarder/7.1.2/Forwarder/Configuretheuniversalforwarder) +* [Enable a receiver](http://docs.splunk.com/Documentation/Forwarder/7.1.2/Forwarder/Enableareceiver) + + +## Jersey Web Server Metrics +Simple Jersey web server metrics for a Tessera node can be monitored if desired. Tessera can store this performance data in a time-series database. Two open-source database options are available for use, depending on your particular use-case: + +* [InfluxDB](https://www.influxdata.com/time-series-platform/influxdb/): For 'push'-style data transmission +* [Prometheus](https://prometheus.io/): For 'pull'-style data transmission + +To set up monitoring requires the installation and configuration of one of these database offerings. Both databases integrate well with the open source metrics dashboard editor [Grafana](https://grafana.com/) to allow for easy creation of dashboards to visualise the data being captured from Tessera. + +### Using InfluxDB +The [InfluxDB documentation](https://docs.influxdata.com/influxdb) provides all the information needed to get InfluxDB setup and ready to integrate with Tessera. A summary of the steps is as follows: + +1. Download and install InfluxDB +1. Create an InfluxDB database +1. Add configuration details to the `server` section of your Tessera config file to allow Tessera to post metrics data to the InfluxDB host. An example configuration using InfluxDB's default hostName and port is (truncated for clarity): + ```json + "server": { + "influxConfig": { + "port": 8086, + "hostName": "http://localhost", + "dbName": "tessera_demo", + "pushIntervalInSecs": 60 + } + } + ``` + With `influxConfig` provided, Tessera will collect metrics data and push it to the InfluxDB service periodically based on the value set for `pushIntervalInSecs` +1. You can use the `influx` CLI to query the database and view the data that is being stored + +### Using Prometheus +The [Prometheus documentation](https://prometheus.io/docs/) provides all the information needed to get Prometheus setup and ready to integrate with Tessera. A summary of the steps is as follows: + +1. Download and install Prometheus +1. Configure `prometheus.yml` to give the Prometheus instance the necessary information to pull metrics from each of the Tessera nodes. As Prometheus is pull-based, no additional config needs to be added to Tessera +1. Go to `localhost:9090` (or whatever host and port have been defined for the Prometheus instance) to see the Prometheus UI and view the data that is being stored + +### Creating dashboards with Grafana +Once Tessera usage data is being stored in either InfluxDB or Prometheus, Grafana can be used to easily create dashboards to visualise that data. The [Grafana documentation](http://docs.grafana.org/) provides all the information needed to set up a Grafana instance and integrate it with both of these time-series databases. A summary of the steps is as follows: + +1. Download and install Grafana +1. Create a new Data Source to connect Grafana with your database of choice +1. Create a new Dashboard +1. Create charts and other elements for the dashboard diff --git a/docs/Quorum Design.png b/docs/Quorum Design.png new file mode 100644 index 000000000..f331ce581 Binary files /dev/null and b/docs/Quorum Design.png differ diff --git a/docs/Quorum_Equity_Use_Case.png b/docs/Quorum_Equity_Use_Case.png new file mode 100644 index 000000000..40d8d287d Binary files /dev/null and b/docs/Quorum_Equity_Use_Case.png differ diff --git a/docs/README.md b/docs/README.md index dd50a04a7..f52813d64 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,4 @@ # Quorum documentation -* [Whitepaper](./Quorum%20Whitepaper%20v0.2.pdf) (PDF) - Quorum Whitepaper [demo video](https://vimeo.com/user5833792/review/210456842/a42d0fcb87) -* [Design](./design.md) - Quorum design overview - * [Raft Specific Documentation](./raft.md) - Overview of raft implementation - * [Istanbul RPC API](./istanbul-rpc-api.md) - Overview of Istanbul BFT APIs -* [Privacy](./privacy.md) - Sending private transactions [privacy video](https://vimeo.com/user5833792/review/210456729/8f70cfaaa5) -* [Running](./running.md) - Detailed instructions for running Quorum nodes (see also [Constellation](https://github.com/jpmorganchase/constellation), [Tessera](https://github.com/jpmorganchase/tessera)) -* [API](./api.md) - new privacy API +New Quorum documentation is now published on https://goquorum.readthedocs.io/ diff --git a/docs/Security/Security & Permissioning.md b/docs/Security/Security & Permissioning.md new file mode 100644 index 000000000..53241593e --- /dev/null +++ b/docs/Security/Security & Permissioning.md @@ -0,0 +1,42 @@ +## Network Permissioning + +Network Permissioning is a feature that controls which nodes can connect to a given node and also to which nodes the given node can dial out to. Currently, it is managed at the individual node level by the `--permissioned` command line flag when starting the node. + +If the `--permissioned` flag is set, the node looks for a file named `/permissioned-nodes.json` . This file contains the whitelist of enodes that this node can connect to and accept connections from. Therefore, with permissioning enabled, only the nodes that are listed in the `permissioned-nodes.json` file become part of the network. If the `--permissioned` flag is specified but no nodes are added to the `permissioned-nodes.json` file then this node can neither connect to any node nor accept any incoming connections. + +The `permissioned-nodes.json` file follows the below pattern, which is similar to the `/static-nodes.json` file that is used to specify the list of static nodes a given node always connects to: + ``` json + [ + "enode://remoteky1@ip1:port1", + "enode://remoteky1@ip2:port2", + "enode://remoteky1@ip3:port3", + ] + ``` + +Sample file: (node id truncated for clarity) + ``` json + [ + "enode://6598638ac5b15ee386210156a43f565fa8c485924894e2f3a967207c047470@127.0.0.1:30300", + ] + ``` + +!!! Note + In the current implementation, every node has its own copy of the `permissioned-nodes.json` file. In this case, if different nodes have a different list of remote keys then each node may have a different list of permissioned nodes - which may have an adverse effect. In a future release, the permissioned nodes list will be moved from the `permissioned-nodes.json` file to a Smart Contract, thereby ensuring that all nodes will use one global on-chain list to verify network connections. + +## Enclave Encryption Technique +The Enclave encrypts payloads sent to it by the Transaction Manager using xsalsa20poly1305 (payload container) and curve25519xsalsa20poly1305 (recipient box). Each payload encryption produces a payload container, as well as N recipient boxes, where N is the number of recipients specified in the `privateFor` param of the Transaction. + + * A payload container contains the payload encrypted with a symmetric key and a random nonce + * A recipient box is the Master Key for the payload container encrypted for the public key of a recipient using a random nonce. (Note that this is basically how PGP works, but using the [NaCl](https://nacl.cr.yp.to/) cryptographic primitives.) + +We currently manually define all public key whitelists, and don’t do automatic rotation of keys, however the system was built to support rotation trivially, by allowing counterparties to advertise multiple keys at once. The tooling to make it seamless and automatic is on the our Roadmap. +We also do not currently have a PKI system, but simply randomly generate keys that are manually added to whitelists (e.g. a registry of authorized counterparties on the blockchain.) The process is currently for operators to generate a keypair and then add the public keys to the whitelists manually. + +## Private Key Storage Algorithm +The following steps detail the technique used to manage the private keys: + + 1. Given a password P + 2. Generate random Argon2i nonce + 3. Generate random NaCl secretbox nonce + 4. Stretch P using Argon2i (and the Argon2i nonce) into a 32-byte master key (MK) + 5. Encrypt Private key in secretbox using secretbox nonce and Argon2i-stretched MK diff --git a/docs/Transaction Processing/Transaction Processing.md b/docs/Transaction Processing/Transaction Processing.md new file mode 100644 index 000000000..82dfed18b --- /dev/null +++ b/docs/Transaction Processing/Transaction Processing.md @@ -0,0 +1,31 @@ +# Transaction Processing + +One of the key features of Quorum is that of Transaction Privacy. To that end, we introduce the notion of 'Public Transactions' and 'Private Transactions'. Note that this is a notional concept only and Quorum does not introduce new Transaction Types, but rather, the Ethereum Transaction Model has been extended to include an optional `privateFor` parameter (the population of which results in a Transaction being treated as private by Quorum) and the Transaction Type has a new `IsPrivate` method to identify such Transactions. + +[Constellation](../../Privacy/Constellation/Constellation) / [Tessera](../../Privacy/Tessera/Tessera) are used by Quorum to transfer private payloads to their intended recipients, performing encryption and related operations in the process. + +## Public Transactions +So called 'Public Transactions' are those Transactions whose payload is visible to all participants of the same Quorum network. These are [created as standard Ethereum Transactions in the usual way](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsendtransaction). + +Examples of Public Transactions may include Market Data updates from some service provider, or some reference data update such as a correction to a Bond Security definition. + +!!! Note + 'Public' Transactions are not Transactions from the public Ethereum network. Perhaps a more appropriate term would be 'common' or 'global' Transactions, but 'Public' is used to contrast with 'Private' Transactions. + +## Private Transactions +So called 'Private Transactions' are those Transactions whose payload is only visible to the network participants whose public keys are specified in the `privateFor` parameter of the Transaction . `privateFor` can take multiple addresses in a comma separated list. (See Creating Private Transactions under the [Running Quorum](../../Getting Started/running) section). + +When the Quorum Node encounters a Transaction with a non-null `privateFor` value, it sets the `V` value of the Transaction Signature to be either `37` or `38` (as opposed to `27` or `28` which are the values used to indicate a Transaction is 'public' as per standard Ethereum as specified in the Ethereum yellow paper). + +## Processing Transactions + +### Public vs Private Transaction handling +Public Transactions are executed in the standard Ethereum way, and so if a Public Transaction is sent to an Account that holds Contract code, each participant will execute the same code and their underlying StateDBs will be updated accordingly. + +Private Transactions, however, are not executed per standard Ethereum: prior to the sender's Quorum Node propagating the Transaction to the rest of the network, it replaces the original Transaction Payload with a hash of the encrypted Payload that it receives from Constellation/Tessera. Participants that are party to the Transaction will be able to replace the hash with the actual payload via their Constellation/Tessera instance, whilst those Participants that are not party will only see the hash. + +The result is that if a Private Transaction is sent to an Account that holds Contract code, those participants who are not party to the Transaction will simply end up skipping the Transaction, and therefore not execute the Contract code. However those participants that are party to the Transaction will replace the hash with the original Payload before calling the EVM for execution, and their StateDB will be updated accordingly. In absence of making corresponding changes to the geth client, these two sets of participants would therefore end up with different StateDBs and not be able to reach consensus. So in order to support this bifurcation of contract state, Quorum stores the state of Public contracts in a Public State Trie that is globally synchronised, and it stores the state of Private contracts in a Private State Trie that is not synchronised globally. For details on how Consensus is achieved in light of this, please refer to [Quorum Consensus](../../Consensus/Consensus). + +### Private Transaction Process Flow + +Please refer [Private Transaction Flow](../../Privacy/Tessera/How%20Tessera%20Works) section under Tessera diff --git a/docs/ZSL-Quorum-POC_Protocol_v0_4.png b/docs/ZSL-Quorum-POC_Protocol_v0_4.png new file mode 100644 index 000000000..5b4e1e50f Binary files /dev/null and b/docs/ZSL-Quorum-POC_Protocol_v0_4.png differ diff --git a/docs/ZSL.md b/docs/ZSL.md new file mode 100644 index 000000000..a81772e74 --- /dev/null +++ b/docs/ZSL.md @@ -0,0 +1,97 @@ +# ZSL Proof of Concept + +!!! caution + The POC discussed in this section should not be considered production-ready + +## Overview +Quorum supports both Public Contracts (which are executed in the standard Ethereum way, and are visible to all participants in the distributed ledger) and Private Contracts (which are shared between the parties to the private contract using Tessera, but can not be read by other participants). This approach preserves the privacy of the parties to the private contract, and the confidentiality of the private contract’s business logic. However, a key limitation is that it does not support prevention of double-spending for digital assets that are exchanged within private contracts. + +ZSL (zero-knowledge security layer) is a protocol designed by the team behind Zcash, that leverages zk-SNARKS to enable the transfer of digital assets on a distributed ledger, without revealing any information about the Sender, Recipient, or the quantity of assets that are being transferred. + +J.P. Morgan and the Zcash team partnered to create a proof of concept (POC) implementation of ZSL for Quorum, which enables the issuance of digital assets using ZSL-enabled public smart contracts (z-contracts). We refer to such digital assets as “z-tokens”. Z-tokens can be shielded from public view and transacted privately. Proof that a shielded transaction has been executed can be presented to a private contract, thereby allowing the private contract to update its state in response to shielded transactions that are executed using public z-contracts. + +This combination of Tessera’s private contracts with ZSL’s z-contracts, allows obligations that arise from a private contract, to be settled using shielded transfers of z-tokens, while maintaining full privacy and confidentiality. + +For more background, please read the [POC Technical Design Document](https://github.com/jpmorganchase/zsl-q/blob/master/docs/ZSL-Quorum-POC_TDD_v1.3pub.pdf). + +## Implementation +The ZSL proof of concept has been implemented as follows: + +* ZSL-specific code resides in the [zsl-q](https://github.com/jpmorganchase/zsl-q) repo +* The Quorum integration is implemented as a separate branch of the Quorum repo - [zsl_geth1.5](https://github.com/jpmorganchase/quorum/tree/zsl_geth1.5) +* There is also a ZSL-specific branch of the quorum-examples repo - [zsl_geth1.5](https://github.com/jpmorganchase/quorum-examples/tree/zsl_geth1.5) +* The [zsl-q-params](https://github.com/jpmorganchase/zsl-q-params) repo contains the shared parameters required for generating and verifying the zk-SNARK proofs. + +Full instructions on how to install Quorum with ZSL can be found in the [zsl-q README](https://github.com/jpmorganchase/zsl-q/blob/master/README.md). + +Please note that this POC is intended to demonstrate how ZSL can complement Quorum, and provide a platform for experimentation and exploration of different use cases. It implements a simplified, stripped-down version of the Zerocash protocol to enable rapid prototyping. There is no formal security proof for the protocol, exception-handling has not been implemented for proof verification, the software has not been subjected to rigorous testing, and **it should not be considered “production-ready”**. + +Broadly speaking, Quorum ZSL supplies a contract within which virtual funds can be "bundled" into cryptographically obfuscated "notes". Each note represents a store of value, and can be unlocked, or "redeemed", only using a secret spending key. To effect a private transfer, Alice may bundle value into a note, and then transmit the note's secret key to Bob through a private, off-chain channel. Bob may then redeem this note on-chain, revealing, in the process, no public link between Alice and himself. Note that in a previous version, a failure to link _Ethereum_ and _note_ signatures made possible a sort of "front-running" attack; this has been fixed by PR [#587](https://github.com/jpmorganchase/quorum/pull/587). + +## Equity Trade use case example +The following example illustrates a specific use case for Quorum with ZSL - a simple equity trade where Alice is buying ACME shares from Bob. The POC includes a demonstration that implements this example; instructions on how to run it can be found [here](https://github.com/jpmorganchase/zsl-q/blob/master/README.md#example-2---private-contract-trade). + +![Quorum Equity Trade Use Case diagram](Quorum_Equity_Use_Case.png) + +### Beginning State: +* Z-contracts have been created for US dollars (the USD z-contract) and ACME shares (the ACME z-contract), +* Z-tokens have been issued into both contracts by the relevant issuer, then shielded and transferred to Alice and Bob. +* Alice owns some USD z-tokens, and Bob owns some ACME z-tokens. Both their holdings are shielded (i.e. a third-party observer cannot tell who owns what). + +### User Story: +1. **A Private Contract is established between Alice and Bob using Tessera.** + 1. The Private Contract specifies an equity trade of a specific quantity of ACME shares at a specific price in USD, between two specific parties: Alice (who is buying the ACME shares) and Bob (who is selling ACME shares). + 1. The Private Contract references the USD and ACME z-contracts, and the relevant public keys and payment addresses of the parties. + 1. One party initialises the contract (this is the equivalent of bidding/offering). It doesn't matter which party does this - in this example, it's Alice. + 1. After being initialised, the contract state is "Bid" (it would be "Offer" if Bob had initialised it). + +2. **The other party sends the Private Contract a transaction indicating acceptance of the terms.** + 1. In this example, it is Bob who accepts Alice’s bid. + 1. At this point, the trade is "done" (i.e. the terms are agreed and both parties have committed to the trade) and all that remains is for Settlement to take place. Assume that the USD must be paid first. + 1. Contract state: Done. + +3. **The Private Contract instructs Payment.** + 1. When the contract's status updates to Done, it issues an instruction to the Buyer's (i.e. Alice’s) client to pay the relevant amount of USD to the Seller (Bob). + 1. Alice's client receives and queues that instruction, and instructs a shielded payment. + +4. **The Buyers pays USD to the Seller.** + 1. Alice pays the relevant amount of USD z-tokens to Bob's USD payment address by generating the necessary zk-SNARK proof and sending it to the USD z-contract. + 1. A shielded transaction takes place, creating a note within the z-contract which only Bob can spend (i.e. Bob’s USD z-token balance is increased). + 1. Alice’s balance of USD z-tokens is reduced accordingly. + +5. **The Buyer provides evidence of payment to the Private Contract.** + 1. Alice sends the Private Contract a transaction with the output note of the USD payment. + 1. This also transmits the note to Bob so he can spend it. + +6. **The Private Contract verifies the payment.** + 1. The Private Contract calls a constant function on the USD z-contract, using the note supplied by Alice, to verify that the payment is valid. + 1. The z-contract responds in a binary fashion to indicate whether the note commitment is in the z-contract’s note accumulator (in which case the shielded payment is valid) or not. + 1. If it is valid, the contract's status updates to Payment Received, and... + +7. **..the Private Contract instructs Delivery.** + 1. The Private Contract issues an instruction to the Seller's (i.e. Bob’s) client to transfer the relevant amount of ACME shares to the Buyer + 1. Bob's client receives and queues that instruction, and prompts him to make the payment. + +8. **The Seller delivers ACME shares to the Buyer.** + 1. Bob transfers the relevant amount of ACME z-tokens to Alice's ACME payment address by generating the necessary zk-SNARK proof and sending it to the ACME z-contract. + 1. A shielded transaction takes place, creating a note output that only Alice can spend (i.e. Alice’s ACME z-token balance is increased). + 1. Bob’s balance of ACME z-tokens is reduced accordingly. + +9. **The Seller provides evidence of delivery to the Private Contract** + 1. Bob sends the Private Contract a transaction with the output note of the ACME delivery. + 1. This also transmits the note to Alice so she can “spend” the note (i.e. transfer those tokens to someone else). + +10. **The Private Contract verifies delivery.** + 1. The Private Contract calls the ACME z-contract (using a constant function), using the note supplied by Bob, to verify that the transfer is valid. + 1. If it is valid, the contract's status updates to Settled. + +After Alice has delivered the USD z-tokens to Bob in step 5, he can send them to a third party (e.g. Carol). + + * Carol will not be able to ascertain the source of the tokens (i.e. that Bob obtained them from Alice). + * Alice will not be able to ascertain when Bob transfers the tokens to someone else (or who the recipient is). She will be able to see that a transaction has occurred (because the transaction is written to the z-contract on the main Quorum chain which she has access to) but she will not be able to ascertain the Sender, Recipient, nor the quantity of tokens being transferred. + * The same holds true for the ACME z-tokens Alice has obtained from Bob. + +### Protocol +The diagram below illustrates how the cryptographic protocol supports steps 1 thru 6 from the example above. + +![ZSL/Quorum Proof of Concept Protocol (v0.4)](ZSL-Quorum-POC_Protocol_v0_4.png) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..84ffa0cd3 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +exclude_patterns = ['/tests'] \ No newline at end of file diff --git a/docs/design.md b/docs/design.md deleted file mode 100644 index a9abe8e9d..000000000 --- a/docs/design.md +++ /dev/null @@ -1,54 +0,0 @@ - -# Design - -## Public/Private State - -Quorum supports dual state: - -- Public state: accessible by all nodes within the network -- Private state: only accessible by nodes with the correct permissions - -The difference is made through the use of transactions with encrypted (private) and non-encrypted payloads (public). -Nodes can determine if a transaction is private by looking at the `v` value of the signature. -Public transactions have a `v` value of `27` or `28`, private transactions have a value of `37` or `38`. - -If the transaction is private, the node can only execute the transaction if it has the ability to decrypt the payload. -Nodes who are not involved in the transaction cannot decrypt the payload and are therefore unable to process the transaction. -As a result all nodes share a common public state which is created through public transactions and have a local unique private state. - -This model imposes a restriction in the ability to modify state in private transactions. -Since it's a common use case for a (private) contract to read data from a public contract the virtual machine has the ability to jump into read only mode. -For each call from a private contract to a public contract the virtual machine will change to read only mode. -If the virtual machine is in read only mode and the code tries to make a state change the virtual machine stops execution and throws an exception. - -The following transactions are allowed: - -``` -1. S -> A -> B -2. S -> (A) -> (B) -3. S -> (A) -> [B -> C] -``` - -and the following transaction are unsupported: - -``` -1. (S) -> A -2. (S) -> (A) -``` - -where: -- `S` = sender -- `(X)` = private -- `X` = public -- `->` = direction -- `[]` = read only mode - -### State verification - -To determine if nodes are in sync the public state root hash is included in the block. -Since private transactions can only be processed by nodes that are involved its impossible to get global consensus on the private state. - -To overcome this issue the RPC method `eth_storageRoot(address[, blockNumber]) -> hash` can be used. -It returns the storage root for the given address at an (optional) block number. -If the optional block number is not given the latest block number is used. -The storage root hash can be on or off chain compared by the parties involved. diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 000000000..28de50d44 Binary files /dev/null and b/docs/images/logo.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..17303439f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,115 @@ +# Quorum - Enterprise Ethereum Client + +## What is Quorum? +Quorum is an Ethereum-based distributed ledger protocol that has been developed to provide industries such as finance, supply chain, retail, real estate, etc. with a permissioned implementation of Ethereum that supports transaction and contract privacy. + +Quorum includes a minimalistic fork of the [Go Ethereum client](https://github.com/ethereum/go-ethereum) (a.k.a geth), and as such, leverages the work that the Ethereum developer community has undertaken. + +The primary features of Quorum, and therefore extensions over public Ethereum, are: + +* Transaction and contract privacy +* Multiple voting-based consensus mechanisms +* Network/Peer permissions management +* Higher performance + +Quorum currently includes the following components: + +* Quorum Node (modified Geth Client) +* Privacy Manager (Constellation/Tessera) + * Transaction Manager + * Enclave + +!!! info "Background Reading" + For more information on the design rationale and background to Quorum, please read the [**Quorum Whitepaper**](https://github.com/jpmorganchase/quorum/blob/master/docs/Quorum%20Whitepaper%20v0.2.pdf), view the [Hyperledger deck](https://drive.google.com/open?id=0B8rVouOzG7cOeHo0M2ZBejZTdGs) or watch the [presentation](https://drive.google.com/open?id=0B8rVouOzG7cOcDg4UkxqdTBacm8) given to the Hyperledger Project Technical Steering Committee meeting on 22-Sept-16. Also see quick overview of sending private transactions [here](https://vimeo.com/user5833792/review/210456729/8f70cfaaa5) + + +## Logical Architecture Diagram +![](Quorum%20Design.png) + +## Design +### Public/Private State + +Quorum supports dual state: + +- Public state: accessible by all nodes within the network +- Private state: only accessible by nodes with the correct permissions + +The difference is made through the use of transactions with encrypted (private) and non-encrypted payloads (public). +Nodes can determine if a transaction is private by looking at the `v` value of the signature. +Public transactions have a `v` value of `27` or `28`, private transactions have a value of `37` or `38`. + +If the transaction is private, the node can only execute the transaction if it has the ability to access and decrypt the payload. Nodes who are not involved in the transaction do not have the private payload at all. As a result all nodes share a common public state which is created through public transactions and have a local unique private state. + +This model imposes a restriction in the ability to modify state in private transactions. +Since it's a common use case for a (private) contract to read data from a public contract the virtual machine has the ability to jump into read only mode. +For each call from a private contract to a public contract the virtual machine will change to read only mode. +If the virtual machine is in read only mode and the code tries to make a state change the virtual machine stops execution and throws an exception. + +The following transactions are allowed: + +``` +1. S -> A -> B +2. S -> (A) -> (B) +3. S -> (A) -> [B -> C] +``` + +and the following transaction are unsupported: + +``` +1. (S) -> A +2. (S) -> (A) +``` + +where: +- `S` = sender +- `(X)` = private +- `X` = public +- `->` = direction +- `[]` = read only mode + +### State verification + +To determine if nodes are in sync the public state root hash is included in the block. +Since private transactions can only be processed by nodes that are involved its impossible to get global consensus on the private state. + +To overcome this issue the RPC method `eth_storageRoot(address[, blockNumber]) -> hash` can be used. +It returns the storage root for the given address at an (optional) block number. +If the optional block number is not given the latest block number is used. +The storage root hash can be on or off chain compared by the parties involved. + +## Component Overview +### Quorum Node +The Quorum Node is intentionally designed to be a lightweight fork of geth in order that it can continue to take advantage of the R&D that is taking place within the ever growing Ethereum community. To that end, Quorum will be updated in-line with future geth releases. + +The Quorum Node includes the following modifications to geth: + + * Consensus is achieved with the Raft or Istanbul BFT consensus algorithms instead of using Proof-of-Work. + * The P2P layer has been modified to only allow connections to/from permissioned nodes. + * The block generation logic has been modified to replace the ‘global state root’ check with a new ‘global public state root’. + * The block validation logic has been modified to replace the ‘global state root’ in the block header with the ‘global public state root’ + * The State Patricia trie has been split into two: a public state trie and a private state trie. + * Block validation logic has been modified to handle ‘Private Transactions’ + * Transaction creation has been modified to allow for Transaction data to be replaced by encrypted hashes in order to preserve private data where required + * The pricing of Gas has been removed, although Gas itself remains + +### Constellation & Tessera +[Constellation](Privacy/Constellation/Constellation) and [Tessera](Privacy/Tessera/Tessera) are Haskell and Java implementations of a general-purpose system for submitting information in a secure way. They are comparable to a network of MTA (Message Transfer Agents) where messages are encrypted with PGP. It is not blockchain-specific, and are potentially applicable in many other types of applications where you want individually-sealed message exchange within a network of counterparties. The Constellation and Tessera modules consist of two sub-modules: + +* The Node (which is used for Quorum's default implementation of a `PrivateTransactionManager`) +* The Enclave + + +#### Transaction Manager +Quorum’s Transaction Manager is responsible for Transaction privacy. It stores and allows access to encrypted transaction data, exchanges encrypted payloads with other participant's Transaction Managers but does not have access to any sensitive private keys. It utilizes the Enclave for cryptographic functionality (although the Enclave can optionally be hosted by the Transaction Manager itself.) + +The Transaction Manager is restful/stateless and can be load balanced easily. + +For further details on how the Transaction Manager interacts with the Enclave, please refer [here](Privacy/Tessera/Tessera%20Services/Transaction%20Manager) + +#### The Enclave + +Distributed Ledger protocols typically leverage cryptographic techniques for transaction authenticity, participant authentication, and historical data preservation (i.e. through a chain of cryptographically hashed data.) In order to achieve a separation of concerns, as well as to provide performance improvements through parallelization of certain crypto-operations, much of the cryptographic work including symmetric key generation and data encryption/decryption is delegated to the Enclave. + +The Enclave works hand in hand with the Transaction Manager to strengthen privacy by managing the encryption/decryption in an isolated way. It holds private keys and is essentially a “virtual HSM” isolated from other components. + +For further details on the Enclave, please refer [here](Privacy/Tessera/Tessera%20Services/Enclave). diff --git a/docs/privacy.md b/docs/privacy.md deleted file mode 100644 index 194ed9191..000000000 --- a/docs/privacy.md +++ /dev/null @@ -1,19 +0,0 @@ - -# Privacy - -## Sending Private Transactions - -To send a private transaction, a private Transaction Manager must be configured. This is the -service which transfers private payloads to their intended recipients, performing -encryption and related operations in the process. - -[Constellation](https://github.com/jpmorganchase/constellation) / [Tessera](https://github.com/jpmorganchase/tessera) is used to provide the private Transaction Manager for a Quorum node. Once a Constellation/Tessera node is running, the `PRIVATE_CONFIG` environment variable is used to point the Quorum node to the transaction manager instance. Examples of this can be seen in the [quorum-examples 7nodes](https://github.com/jpmorganchase/quorum-examples) source files. - -Once Constellation/Tessera is launched and `PRIVATE_CONFIG` points to a valid configuration file, -a `SendTransaction` call can be made private by specifying the `privateFor` argument. -`privateFor` is a list of public keys of the intended recipients (these public keys are distinct from Ethereum account keys). When a transaction -is private, the transaction contents will be sent to the `PrivateTransactionManager` and the -identifier returned will be placed in the transaction instead. When other Quorum nodes -receive a private transaction, they will query their `PrivateTransactionManager` for the -identifier and replace the transaction contents with the result. Nodes which are -not party to a transaction will not be able to retrieve the original contents. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..62b0ceade --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,7 @@ +mkdocs>=1.0 +pymdown-extensions==6.0 +mkdocs-material>=4.1 +Markdown==3.0.1 +markdown-fenced-code-tabs==1.0.5 +markdown-include==0.5.1 +MarkupSafe==1.1.0 diff --git a/docs/running.md b/docs/running.md deleted file mode 100644 index ce3f6ebba..000000000 --- a/docs/running.md +++ /dev/null @@ -1,107 +0,0 @@ - -# Running Quorum - -Quorum introduces the `--permissioned` CLI argument: - -``` -QUORUM OPTIONS: - --permissioned If enabled, the node will allow only a defined list of nodes to connect -``` - -The full list of arguments can be viewed by running `geth --help`. - -### Initialize chain - -The first step is to generate the genesis block. - -The `7nodes` directory in the `quorum-examples` repository contains several keys (using an empty password) that are used in the example genesis file: - -``` -key1 vote key 1 -key2 vote key 2 -key3 vote key 3 -key4 block maker 1 -key5 block maker 2 -``` - -Example genesis file (copy to `genesis.json`): -```json -{ - "alloc": {}, - "coinbase": "0x0000000000000000000000000000000000000000", - "config": { - "homesteadBlock": 0 - }, - "difficulty": "0x0", - "extraData": "0x", - "gasLimit": "0x2FEFD800", - "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", - "nonce": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x00" -} -``` - -Now we can initialize geth: - -``` -geth init genesis.json -``` - -### Setup Bootnode -Optionally you can set up a bootnode that all the other nodes will first connect to in order to find other peers in the network. You will first need to generate a bootnode key: - -1. To generate the key for the first time: - - `bootnode -genkey tmp_file.txt // this will start a bootnode with an enode address and generate a key inside a “tmp_file.txt” file` - -2. To later restart the bootnode using the same key (and hence use the same enode url): - - `bootnode -nodekey tmp_file.txt` - - or - - `bootnode -nodekeyhex 77bd02ffa26e3fb8f324bda24ae588066f1873d95680104de5bc2db9e7b2e510 // Key from tmp_file.txt` - - -### Start node - -Starting a node is as simple as `geth`. This will start the node without any of the roles and makes the node a spectator. If you have setup a bootnode then be sure to add the `--bootnodes` param to your startup command: - -`geth --bootnodes $BOOTNODE_ENODE` - -## Setup multi-node network - -The [quorum-examples 7nodes](https://github.com/jpmorganchase/quorum-examples) source files contain several scripts demonstrating how to set up a private test network made up of 7 nodes. - -## Permissioned Network - -Node Permissioning is a feature of Quorum that is used to define: -1. The nodes that a particular Quorum node is able to connect to -2. The nodes that a particular Quorum node is able to receive connections from - -Permissioning is managed at the individual node level by using the `--permissioned` command line flag when starting the node. - -If a node is started with `--permissioned` set, the node will look for a `/permissioned-nodes.json` file. This file contains the list of enodes that this node can connect to and accept connections from. In other words, if permissioning is enabled, only the nodes that are listed in the `permissioned-nodes.json` file become part of the network. - -If `--permissioned` is set, a `permissioned-nodes.json` file must be provided. If the flag is set but no nodes are present in this file, then the node will be unable to make any outward connections or accept any incoming connections. - -The format of `permissioned-nodes.json` is similar to `static-nodes.json`: - -```json -[ - "enode://enodehash1@ip1:port1", - "enode://enodehash2@ip2:port2", - "enode://enodehash3@ip3:port3" -] -``` - -For example, including the hash, a sample file might look like: - -```json -[ - "enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300" -] -``` - -In the current release, every node has its own copy of `permissioned-nodes.json`. In a future release, the permissioned nodes list will be moved to a smart contract, thereby keeping the list on-chain and requiring just one global list of nodes that connect to the network. diff --git a/docs/theme/assets/javascripts/application.81068b3a.js b/docs/theme/assets/javascripts/application.81068b3a.js new file mode 100644 index 000000000..476bcf8ae --- /dev/null +++ b/docs/theme/assets/javascripts/application.81068b3a.js @@ -0,0 +1,6 @@ +!function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=n,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=13)}([function(e,t,n){"use strict";var r={Listener:function(){function e(e,t,n){var r=this;this.els_=Array.prototype.slice.call("string"==typeof e?document.querySelectorAll(e):[].concat(e)),this.handler_="function"==typeof n?{update:n}:n,this.events_=[].concat(t),this.update_=function(e){return r.handler_.update(e)}}var t=e.prototype;return t.listen=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.addEventListener(e,n.update_,!1)})}),"function"==typeof this.handler_.setup&&this.handler_.setup()},t.unlisten=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.removeEventListener(e,n.update_)})}),"function"==typeof this.handler_.reset&&this.handler_.reset()},e}(),MatchMedia:function(e,t){this.handler_=function(e){e.matches?t.listen():t.unlisten()};var n=window.matchMedia(e);n.addListener(this.handler_),this.handler_(n)}},i={Shadow:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement&&n.parentNode instanceof HTMLElement))throw new ReferenceError;if(this.el_=n.parentNode,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLElement))throw new ReferenceError;this.header_=n,this.height_=0,this.active_=!1}var t=e.prototype;return t.setup=function(){for(var e=this.el_;e=e.previousElementSibling;){if(!(e instanceof HTMLElement))throw new ReferenceError;this.height_+=e.offsetHeight}this.update()},t.update=function(e){if(!e||"resize"!==e.type&&"orientationchange"!==e.type){var t=window.pageYOffset>=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=30&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))throw new TypeError;if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+-43;e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(24)}).call(this,n(4))},function(e,f,d){"use strict";(function(t){var e=d(8),n=setTimeout;function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],u(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void c(t);if("function"==typeof n)return void u((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,c(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,c(e)}function c(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],r.forEach(function(e,t){var n,r=a.docs_.get(t),i=f.createElement("li",{class:"md-search-result__item"},f.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article md-search-result__article--document"},f.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(f.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article"},f.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var o=this.el_.parentNode;if(!(o instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&o.offsetHeight>=o.scrollHeight-16;)this.stack_.shift()();var l=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(l,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),r.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",r.size)}}}else{var u=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(u):u(a.data_)},250)}},e}()}).call(this,r(3))},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(14),c(15),c(16),c(17),c(18),c(19),c(20);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))}).listen(),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&document.activeElement.isContentEditable||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else if(document.activeElement&&!document.activeElement.form){if("TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName)return;70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault())}}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){e.exports=n.p+"assets/images/icons/bitbucket.1b09e088.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/github.f0b8504a.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/gitlab.6dd19c00.svg"},function(e,t){e.exports="/Users/chrishounsom/mkdocs-material/material/application.668e8dde.css"},function(e,t){e.exports="/Users/chrishounsom/mkdocs-material/material/application-palette.224b79ff.css"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return(t=t||{}).bubbles=!!t.bubbles,t.cancelable=!!t.cancelable,(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(22),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset .critic.comment:before,.md-typeset .footnote-backref,.md-typeset .task-list-control .task-list-indicator:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before,.md-typeset summary:after{font-family:Material Icons;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-content__icon,.md-footer-nav__button,.md-header-nav__button,.md-nav__button,.md-nav__title:before,.md-search-result__article--document:before{display:inline-block;margin:.2rem;padding:.4rem;font-size:1.2rem;cursor:pointer}.md-icon--arrow-back:before{content:""}.md-icon--arrow-forward:before{content:""}.md-icon--menu:before{content:""}.md-icon--search:before{content:""}[dir=rtl] .md-icon--arrow-back:before{content:""}[dir=rtl] .md-icon--arrow-forward:before{content:""}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern","liga";font-feature-settings:"kern","liga";font-family:Helvetica Neue,Helvetica,Arial,sans-serif}code,kbd,pre{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern";font-feature-settings:"kern";font-family:Courier New,Courier,monospace}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact}.md-typeset blockquote,.md-typeset ol,.md-typeset p,.md-typeset ul{margin:1em 0}.md-typeset h1{margin:0 0 2rem;color:rgba(0,0,0,.54);font-size:1.5625rem;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:2rem 0 .8rem;font-size:1.25rem;line-height:1.4}.md-typeset h3{margin:1.6rem 0 .8rem;font-size:1rem;font-weight:400;letter-spacing:-.01em;line-height:1.5}.md-typeset h2+h3{margin-top:.8rem}.md-typeset h4{font-size:.8rem}.md-typeset h4,.md-typeset h5,.md-typeset h6{margin:.8rem 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset h5{text-transform:uppercase}.md-typeset hr{margin:1.5em 0;border-bottom:.05rem dotted rgba(0,0,0,.26)}.md-typeset a{color:#5700ff;word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color .125s}.md-typeset a:active,.md-typeset a:hover{color:#00e290}.md-typeset code,.md-typeset pre{background-color:hsla(0,0%,92.5%,.5);color:#37474f;font-size:85%;direction:ltr}.md-typeset code{margin:0 .29412em;padding:.07353em 0;border-radius:.1rem;box-shadow:.29412em 0 0 hsla(0,0%,92.5%,.5),-.29412em 0 0 hsla(0,0%,92.5%,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:0;background-color:transparent;box-shadow:none}.md-typeset a>code{margin:inherit;padding:inherit;border-radius:initial;background-color:inherit;color:inherit;box-shadow:none}.md-typeset pre{position:relative;margin:1em 0;border-radius:.1rem;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset pre>code{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;font-size:inherit;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;overflow:auto}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:#00e290}.md-typeset kbd{padding:0 .29412em;border-radius:.15rem;border:.05rem solid #c9c9c9;border-bottom-color:#bcbcbc;background-color:#fcfcfc;color:#555;font-size:85%;box-shadow:0 .05rem 0 #b0b0b0;word-break:break-word}.md-typeset mark{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;background-color:rgba(255,235,59,.5);box-shadow:.25em 0 0 rgba(255,235,59,.5),-.25em 0 0 rgba(255,235,59,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{border-bottom:.05rem dotted rgba(0,0,0,.54);text-decoration:none;cursor:help}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.07812em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.07812em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;border-left:.2rem solid rgba(0,0,0,.26);color:rgba(0,0,0,.54)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid rgba(0,0,0,.26);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset iframe,.md-typeset img,.md-typeset svg{max-width:100%}.md-typeset table:not([class]){box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);display:inline-block;max-width:100%;border-radius:.1rem;font-size:.64rem;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.6rem .8rem;background-color:rgba(0,0,0,.54);color:#fff;vertical-align:top}.md-typeset table:not([class]) td{padding:.6rem .8rem;border-top:.05rem solid rgba(0,0,0,.07);vertical-align:top}.md-typeset table:not([class]) tr{transition:background-color .125s}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:inset 0 .05rem 0 #fff}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;-webkit-overflow-scrolling:touch}.md-typeset .md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}.md-typeset .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{font-size:125%;overflow-x:hidden}body,html{height:100%}body{position:relative;font-size:.5rem}hr{display:block;height:.05rem;padding:0;border:0}.md-svg{display:none}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container,.md-main{overflow:auto}.md-container{display:table;width:100%;height:100%;padding-top:2.4rem;table-layout:fixed}.md-main{display:table-row;height:100%}.md-main__inner{height:100%;padding-top:1.5rem;padding-bottom:.05rem}.md-toggle{display:none}.md-overlay{position:fixed;top:0;width:0;height:0;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);opacity:0;z-index:3}.md-flex{display:table}.md-flex__cell{display:table-cell;position:relative;vertical-align:top}.md-flex__cell--shrink{width:0}.md-flex__cell--stretch{display:table;width:100%;table-layout:fixed}.md-flex__ellipsis{display:table-cell;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-skip{position:fixed;width:.05rem;height:.05rem;margin:.5rem;padding:.3rem .5rem;-webkit-transform:translateY(.4rem);transform:translateY(.4rem);border-radius:.1rem;background-color:rgba(0,0,0,.87);color:#fff;font-size:.64rem;opacity:0;overflow:hidden}.md-skip:focus{width:auto;height:auto;clip:auto;-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;z-index:10}@page{margin:25mm}.md-clipboard{position:absolute;top:.3rem;right:.3rem;width:1.4rem;height:1.4rem;border-radius:.1rem;font-size:.8rem;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-clipboard:before{transition:color .25s,opacity .25s;color:rgba(0,0,0,.07);content:"\E14D"}.codehilite:hover .md-clipboard:before,.md-typeset .highlight:hover .md-clipboard:before,pre:hover .md-clipboard:before{color:rgba(0,0,0,.54)}.md-clipboard:focus:before,.md-clipboard:hover:before{color:#00e290}.md-clipboard__message{display:block;position:absolute;top:0;right:1.7rem;padding:.3rem .5rem;-webkit-transform:translateX(.4rem);transform:translateX(.4rem);transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s;transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);border-radius:.1rem;background-color:rgba(0,0,0,.54);color:#fff;font-size:.64rem;white-space:nowrap;opacity:0;pointer-events:none}.md-clipboard__message--active{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;pointer-events:auto}.md-clipboard__message:before{content:attr(aria-label)}.md-clipboard__message:after{display:block;position:absolute;top:50%;right:-.2rem;width:0;margin-top:-.2rem;border-color:transparent rgba(0,0,0,.54);border-style:solid;border-width:.2rem 0 .2rem .2rem;content:""}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__icon{position:relative;margin:.4rem 0;padding:0;float:right}.md-typeset .md-content__icon{color:rgba(0,0,0,.26)}.md-header{position:fixed;top:0;right:0;left:0;height:2.4rem;transition:background-color .25s,color .25s;background-color:#5700ff;color:#fff;box-shadow:none;z-index:2;-webkit-backface-visibility:hidden;backface-visibility:hidden}.no-js .md-header{transition:none;box-shadow:none}.md-header[data-md-state=shadow]{transition:background-color .25s,color .25s,box-shadow .25s;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.md-header-nav{padding:0 .2rem}.md-header-nav__button{position:relative;transition:opacity .25s;z-index:1}.md-header-nav__button:hover{opacity:.7}.md-header-nav__button.md-logo *{display:block}.no-js .md-header-nav__button.md-icon--search{display:none}.md-header-nav__topic{display:block;position:absolute;transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem)}.no-js .md-header-nav__topic{position:static}.no-js .md-header-nav__topic+.md-header-nav__topic{display:none}.md-header-nav__title{padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.md-header-nav__title[data-md-state=active] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);opacity:1;z-index:0;pointer-events:auto}.md-header-nav__source{display:none}.md-hero{transition:background .25s;background-color:#5700ff;color:#fff;font-size:1rem;overflow:hidden}.md-hero__inner{margin-top:1rem;padding:.8rem .8rem .4rem;transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition-delay:.1s}[data-md-state=hidden] .md-hero__inner{pointer-events:none;-webkit-transform:translateY(.625rem);transform:translateY(.625rem);transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:transform 0s .4s,opacity .1s 0s;transition:transform 0s .4s,opacity .1s 0s,-webkit-transform 0s .4s;opacity:0}.md-hero--expand .md-hero__inner{margin-bottom:1.2rem}.md-footer-nav{background-color:rgba(0,0,0,.87);color:#fff}.md-footer-nav__inner{padding:.2rem;overflow:auto}.md-footer-nav__link{padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}.md-footer-nav__link:hover{opacity:.7}.md-footer-nav__link--prev{width:25%;float:left}[dir=rtl] .md-footer-nav__link--prev{float:right}.md-footer-nav__link--next{width:75%;float:right;text-align:right}[dir=rtl] .md-footer-nav__link--next{float:left;text-align:left}.md-footer-nav__button{transition:background .25s}.md-footer-nav__title{position:relative;padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer-nav__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;color:hsla(0,0%,100%,.7);font-size:.75rem}.md-footer-meta{background-color:rgba(0,0,0,.895)}.md-footer-meta__inner{padding:.2rem;overflow:auto}.md-footer-meta__help{background-color:#00e290}html .md-footer-meta.md-typeset .md-footer-meta__help a{margin:0 .6rem;color:rgba(0,0,0,.54)}html .md-footer-meta.md-typeset .md-footer-meta__help a:focus,html .md-footer-meta.md-typeset .md-footer-meta__help a:hover{color:#fff}html .md-footer-meta.md-typeset a{color:hsla(0,0%,100%,.7)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:#fff}.md-footer-copyright{margin:0 .6rem;padding:.4rem 0;color:hsla(0,0%,100%,.3);font-size:.64rem}.md-footer-copyright__highlight{color:hsla(0,0%,100%,.7)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;font-size:.8rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden}.md-nav__title:before{display:none;content:"\E5C4"}[dir=rtl] .md-nav__title:before{content:"\E5C8"}.md-nav__title .md-nav__button{display:none}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item:last-child{padding-bottom:.6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__item .md-nav__item:last-child{padding-bottom:0}.md-nav__button img{width:100%;height:auto}.md-nav__link{display:block;margin-top:.625em;transition:color .125s;text-overflow:ellipsis;cursor:pointer;overflow:hidden}.md-nav__item--nested>.md-nav__link:after{content:"\E313"}html .md-nav__link[for=__toc],html .md-nav__link[for=__toc]+.md-nav__link:after,html .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link[data-md-state=blur]{color:rgba(0,0,0,.54)}.md-nav__link--active,.md-nav__link:active{color:#5700ff}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:#00e290}.md-nav__source,.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}.md-search__form{position:relative}.md-search__input{position:relative;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;z-index:2}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input:-ms-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-ms-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-webkit-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input:-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-clear{display:none}.md-search__icon{position:absolute;transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;font-size:1.2rem;cursor:pointer;z-index:2}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}.md-search__icon[for=__search]:before{content:"\E8B6"}.md-search__icon[type=reset]{top:.3rem;right:.5rem;-webkit-transform:scale(.125);transform:scale(.125);transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);opacity:0}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{-webkit-transform:scale(1);transform:scale(1);opacity:1}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;width:100%;border-radius:0 0 .1rem .1rem;overflow:hidden;z-index:1}.md-search__scrollwrap{height:100%;background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07);overflow-y:auto;-webkit-overflow-scrolling:touch}.md-search-result{color:rgba(0,0,0,.87);word-break:break-word}.md-search-result__meta{padding:0 .8rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.8rem}.md-search-result__list{margin:0;padding:0;border-top:.05rem solid rgba(0,0,0,.07);list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 rgba(0,0,0,.07)}.md-search-result__link{display:block;transition:background .25s;outline:0;overflow:hidden}.md-search-result__link:hover,.md-search-result__link[data-md-state=active]{background-color:rgba(0,226,144,.1)}.md-search-result__link:hover .md-search-result__article:before,.md-search-result__link[data-md-state=active] .md-search-result__article:before{opacity:.7}.md-search-result__link:last-child .md-search-result__teaser{margin-bottom:.6rem}.md-search-result__article{position:relative;padding:0 .8rem;overflow:auto}.md-search-result__article--document:before{position:absolute;left:0;margin:.1rem;transition:opacity .25s;color:rgba(0,0,0,.54);content:"\E880"}[dir=rtl] .md-search-result__article--document:before{right:0;left:auto}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-size:.8rem;font-weight:400;line-height:1.4}.md-search-result__title{margin:.5em 0;font-size:.64rem;font-weight:700;line-height:1.4}.md-search-result__teaser{display:-webkit-box;max-height:1.65rem;margin:.5em 0;color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.4;text-overflow:ellipsis;overflow:hidden;-webkit-line-clamp:2}.md-search-result em{font-style:normal;font-weight:700;text-decoration:underline}.md-sidebar{position:absolute;width:12.1rem;padding:1.2rem 0;overflow:hidden}.md-sidebar[data-md-state=lock]{position:fixed;top:2.4rem}.md-sidebar--secondary{display:none}.md-sidebar__scrollwrap{max-height:100%;margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00e290}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.md-source{display:block;padding-right:.6rem;transition:opacity .25s;font-size:.65rem;line-height:1.2;white-space:nowrap}[dir=rtl] .md-source{padding-right:0;padding-left:.6rem}.md-source:hover{opacity:.7}.md-source:after,.md-source__icon{display:inline-block;height:2.4rem;content:"";vertical-align:middle}.md-source__icon{width:2.4rem}.md-source__icon svg{width:1.2rem;height:1.2rem;margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2.2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2.2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:100%;margin-left:.6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden;vertical-align:middle}.md-source__facts{margin:0;padding:0;font-size:.55rem;font-weight:700;list-style-type:none;opacity:.75;overflow:hidden}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"\00B7"}.md-source__fact:first-child:before{display:none}.md-source-file{display:inline-block;margin:1em .5em 1em 0;padding-right:.25rem;border-radius:.1rem;background-color:rgba(0,0,0,.07);font-size:.64rem;list-style-type:none;cursor:pointer;overflow:hidden}.md-source-file:before{display:inline-block;margin-right:.25rem;padding:.25rem;background-color:rgba(0,0,0,.26);color:#fff;font-size:.8rem;content:"\E86F";vertical-align:middle}html .md-source-file{transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1)}html .md-source-file:before{transition:inherit}html body .md-typeset .md-source-file{color:rgba(0,0,0,.54)}.md-source-file:hover{box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36)}.md-source-file:hover:before{background-color:#00e290}.md-tabs{width:100%;transition:background .25s;background-color:#5700ff;color:#fff;overflow:auto}.md-tabs__list{margin:0 0 0 .2rem;padding:0;list-style:none;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);font-size:.7rem;opacity:.7}.md-tabs__link--active,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:.02s}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:.04s}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:.06s}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:.08s}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs[data-md-state=hidden] .md-tabs__link{-webkit-transform:translateY(50%);transform:translateY(50%);transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,transform 0s .4s,opacity .1s;transition:color .25s,transform 0s .4s,opacity .1s,-webkit-transform 0s .4s;opacity:0}.md-typeset .admonition,.md-typeset details{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin:1.5625em 0;padding:0 .6rem;border-left:.2rem solid #448aff;border-radius:.1rem;font-size:.64rem;overflow:auto}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition>.admonition-title,.md-typeset .admonition>summary,.md-typeset details>.admonition-title,.md-typeset details>summary{margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;border-bottom:.05rem solid rgba(68,138,255,.1);background-color:rgba(68,138,255,.1);font-weight:700}[dir=rtl] .md-typeset .admonition>.admonition-title,[dir=rtl] .md-typeset .admonition>summary,[dir=rtl] .md-typeset details>.admonition-title,[dir=rtl] .md-typeset details>summary{padding:.4rem 2rem .4rem .6rem}.md-typeset .admonition>.admonition-title:last-child,.md-typeset .admonition>summary:last-child,.md-typeset details>.admonition-title:last-child,.md-typeset details>summary:last-child{margin-bottom:0}.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before{position:absolute;left:.6rem;color:#448aff;font-size:1rem;content:"\E3C9"}[dir=rtl] .md-typeset .admonition>.admonition-title:before,[dir=rtl] .md-typeset .admonition>summary:before,[dir=rtl] .md-typeset details>.admonition-title:before,[dir=rtl] .md-typeset details>summary:before{right:.6rem;left:auto}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-left-color:#00b0ff}[dir=rtl] .md-typeset .admonition.abstract,[dir=rtl] .md-typeset .admonition.summary,[dir=rtl] .md-typeset .admonition.tldr,[dir=rtl] .md-typeset details.abstract,[dir=rtl] .md-typeset details.summary,[dir=rtl] .md-typeset details.tldr{border-right-color:#00b0ff}.md-typeset .admonition.abstract>.admonition-title,.md-typeset .admonition.abstract>summary,.md-typeset .admonition.summary>.admonition-title,.md-typeset .admonition.summary>summary,.md-typeset .admonition.tldr>.admonition-title,.md-typeset .admonition.tldr>summary,.md-typeset details.abstract>.admonition-title,.md-typeset details.abstract>summary,.md-typeset details.summary>.admonition-title,.md-typeset details.summary>summary,.md-typeset details.tldr>.admonition-title,.md-typeset details.tldr>summary{border-bottom-color:rgba(0,176,255,.1);background-color:rgba(0,176,255,.1)}.md-typeset .admonition.abstract>.admonition-title:before,.md-typeset .admonition.abstract>summary:before,.md-typeset .admonition.summary>.admonition-title:before,.md-typeset .admonition.summary>summary:before,.md-typeset .admonition.tldr>.admonition-title:before,.md-typeset .admonition.tldr>summary:before,.md-typeset details.abstract>.admonition-title:before,.md-typeset details.abstract>summary:before,.md-typeset details.summary>.admonition-title:before,.md-typeset details.summary>summary:before,.md-typeset details.tldr>.admonition-title:before,.md-typeset details.tldr>summary:before{color:#00b0ff;content:""}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-left-color:#00b8d4}[dir=rtl] .md-typeset .admonition.info,[dir=rtl] .md-typeset .admonition.todo,[dir=rtl] .md-typeset details.info,[dir=rtl] .md-typeset details.todo{border-right-color:#00b8d4}.md-typeset .admonition.info>.admonition-title,.md-typeset .admonition.info>summary,.md-typeset .admonition.todo>.admonition-title,.md-typeset .admonition.todo>summary,.md-typeset details.info>.admonition-title,.md-typeset details.info>summary,.md-typeset details.todo>.admonition-title,.md-typeset details.todo>summary{border-bottom-color:rgba(0,184,212,.1);background-color:rgba(0,184,212,.1)}.md-typeset .admonition.info>.admonition-title:before,.md-typeset .admonition.info>summary:before,.md-typeset .admonition.todo>.admonition-title:before,.md-typeset .admonition.todo>summary:before,.md-typeset details.info>.admonition-title:before,.md-typeset details.info>summary:before,.md-typeset details.todo>.admonition-title:before,.md-typeset details.todo>summary:before{color:#00b8d4;content:""}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-left-color:#00bfa5}[dir=rtl] .md-typeset .admonition.hint,[dir=rtl] .md-typeset .admonition.important,[dir=rtl] .md-typeset .admonition.tip,[dir=rtl] .md-typeset details.hint,[dir=rtl] .md-typeset details.important,[dir=rtl] .md-typeset details.tip{border-right-color:#00bfa5}.md-typeset .admonition.hint>.admonition-title,.md-typeset .admonition.hint>summary,.md-typeset .admonition.important>.admonition-title,.md-typeset .admonition.important>summary,.md-typeset .admonition.tip>.admonition-title,.md-typeset .admonition.tip>summary,.md-typeset details.hint>.admonition-title,.md-typeset details.hint>summary,.md-typeset details.important>.admonition-title,.md-typeset details.important>summary,.md-typeset details.tip>.admonition-title,.md-typeset details.tip>summary{border-bottom-color:rgba(0,191,165,.1);background-color:rgba(0,191,165,.1)}.md-typeset .admonition.hint>.admonition-title:before,.md-typeset .admonition.hint>summary:before,.md-typeset .admonition.important>.admonition-title:before,.md-typeset .admonition.important>summary:before,.md-typeset .admonition.tip>.admonition-title:before,.md-typeset .admonition.tip>summary:before,.md-typeset details.hint>.admonition-title:before,.md-typeset details.hint>summary:before,.md-typeset details.important>.admonition-title:before,.md-typeset details.important>summary:before,.md-typeset details.tip>.admonition-title:before,.md-typeset details.tip>summary:before{color:#00bfa5;content:""}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-left-color:#00c853}[dir=rtl] .md-typeset .admonition.check,[dir=rtl] .md-typeset .admonition.done,[dir=rtl] .md-typeset .admonition.success,[dir=rtl] .md-typeset details.check,[dir=rtl] .md-typeset details.done,[dir=rtl] .md-typeset details.success{border-right-color:#00c853}.md-typeset .admonition.check>.admonition-title,.md-typeset .admonition.check>summary,.md-typeset .admonition.done>.admonition-title,.md-typeset .admonition.done>summary,.md-typeset .admonition.success>.admonition-title,.md-typeset .admonition.success>summary,.md-typeset details.check>.admonition-title,.md-typeset details.check>summary,.md-typeset details.done>.admonition-title,.md-typeset details.done>summary,.md-typeset details.success>.admonition-title,.md-typeset details.success>summary{border-bottom-color:rgba(0,200,83,.1);background-color:rgba(0,200,83,.1)}.md-typeset .admonition.check>.admonition-title:before,.md-typeset .admonition.check>summary:before,.md-typeset .admonition.done>.admonition-title:before,.md-typeset .admonition.done>summary:before,.md-typeset .admonition.success>.admonition-title:before,.md-typeset .admonition.success>summary:before,.md-typeset details.check>.admonition-title:before,.md-typeset details.check>summary:before,.md-typeset details.done>.admonition-title:before,.md-typeset details.done>summary:before,.md-typeset details.success>.admonition-title:before,.md-typeset details.success>summary:before{color:#00c853;content:""}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-left-color:#64dd17}[dir=rtl] .md-typeset .admonition.faq,[dir=rtl] .md-typeset .admonition.help,[dir=rtl] .md-typeset .admonition.question,[dir=rtl] .md-typeset details.faq,[dir=rtl] .md-typeset details.help,[dir=rtl] .md-typeset details.question{border-right-color:#64dd17}.md-typeset .admonition.faq>.admonition-title,.md-typeset .admonition.faq>summary,.md-typeset .admonition.help>.admonition-title,.md-typeset .admonition.help>summary,.md-typeset .admonition.question>.admonition-title,.md-typeset .admonition.question>summary,.md-typeset details.faq>.admonition-title,.md-typeset details.faq>summary,.md-typeset details.help>.admonition-title,.md-typeset details.help>summary,.md-typeset details.question>.admonition-title,.md-typeset details.question>summary{border-bottom-color:rgba(100,221,23,.1);background-color:rgba(100,221,23,.1)}.md-typeset .admonition.faq>.admonition-title:before,.md-typeset .admonition.faq>summary:before,.md-typeset .admonition.help>.admonition-title:before,.md-typeset .admonition.help>summary:before,.md-typeset .admonition.question>.admonition-title:before,.md-typeset .admonition.question>summary:before,.md-typeset details.faq>.admonition-title:before,.md-typeset details.faq>summary:before,.md-typeset details.help>.admonition-title:before,.md-typeset details.help>summary:before,.md-typeset details.question>.admonition-title:before,.md-typeset details.question>summary:before{color:#64dd17;content:""}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-left-color:#ff9100}[dir=rtl] .md-typeset .admonition.attention,[dir=rtl] .md-typeset .admonition.caution,[dir=rtl] .md-typeset .admonition.warning,[dir=rtl] .md-typeset details.attention,[dir=rtl] .md-typeset details.caution,[dir=rtl] .md-typeset details.warning{border-right-color:#ff9100}.md-typeset .admonition.attention>.admonition-title,.md-typeset .admonition.attention>summary,.md-typeset .admonition.caution>.admonition-title,.md-typeset .admonition.caution>summary,.md-typeset .admonition.warning>.admonition-title,.md-typeset .admonition.warning>summary,.md-typeset details.attention>.admonition-title,.md-typeset details.attention>summary,.md-typeset details.caution>.admonition-title,.md-typeset details.caution>summary,.md-typeset details.warning>.admonition-title,.md-typeset details.warning>summary{border-bottom-color:rgba(255,145,0,.1);background-color:rgba(255,145,0,.1)}.md-typeset .admonition.attention>.admonition-title:before,.md-typeset .admonition.attention>summary:before,.md-typeset .admonition.caution>.admonition-title:before,.md-typeset .admonition.caution>summary:before,.md-typeset .admonition.warning>.admonition-title:before,.md-typeset .admonition.warning>summary:before,.md-typeset details.attention>.admonition-title:before,.md-typeset details.attention>summary:before,.md-typeset details.caution>.admonition-title:before,.md-typeset details.caution>summary:before,.md-typeset details.warning>.admonition-title:before,.md-typeset details.warning>summary:before{color:#ff9100;content:""}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-left-color:#ff5252}[dir=rtl] .md-typeset .admonition.fail,[dir=rtl] .md-typeset .admonition.failure,[dir=rtl] .md-typeset .admonition.missing,[dir=rtl] .md-typeset details.fail,[dir=rtl] .md-typeset details.failure,[dir=rtl] .md-typeset details.missing{border-right-color:#ff5252}.md-typeset .admonition.fail>.admonition-title,.md-typeset .admonition.fail>summary,.md-typeset .admonition.failure>.admonition-title,.md-typeset .admonition.failure>summary,.md-typeset .admonition.missing>.admonition-title,.md-typeset .admonition.missing>summary,.md-typeset details.fail>.admonition-title,.md-typeset details.fail>summary,.md-typeset details.failure>.admonition-title,.md-typeset details.failure>summary,.md-typeset details.missing>.admonition-title,.md-typeset details.missing>summary{border-bottom-color:rgba(255,82,82,.1);background-color:rgba(255,82,82,.1)}.md-typeset .admonition.fail>.admonition-title:before,.md-typeset .admonition.fail>summary:before,.md-typeset .admonition.failure>.admonition-title:before,.md-typeset .admonition.failure>summary:before,.md-typeset .admonition.missing>.admonition-title:before,.md-typeset .admonition.missing>summary:before,.md-typeset details.fail>.admonition-title:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>.admonition-title:before,.md-typeset details.failure>summary:before,.md-typeset details.missing>.admonition-title:before,.md-typeset details.missing>summary:before{color:#ff5252;content:""}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-left-color:#ff1744}[dir=rtl] .md-typeset .admonition.danger,[dir=rtl] .md-typeset .admonition.error,[dir=rtl] .md-typeset details.danger,[dir=rtl] .md-typeset details.error{border-right-color:#ff1744}.md-typeset .admonition.danger>.admonition-title,.md-typeset .admonition.danger>summary,.md-typeset .admonition.error>.admonition-title,.md-typeset .admonition.error>summary,.md-typeset details.danger>.admonition-title,.md-typeset details.danger>summary,.md-typeset details.error>.admonition-title,.md-typeset details.error>summary{border-bottom-color:rgba(255,23,68,.1);background-color:rgba(255,23,68,.1)}.md-typeset .admonition.danger>.admonition-title:before,.md-typeset .admonition.danger>summary:before,.md-typeset .admonition.error>.admonition-title:before,.md-typeset .admonition.error>summary:before,.md-typeset details.danger>.admonition-title:before,.md-typeset details.danger>summary:before,.md-typeset details.error>.admonition-title:before,.md-typeset details.error>summary:before{color:#ff1744;content:""}.md-typeset .admonition.bug,.md-typeset details.bug{border-left-color:#f50057}[dir=rtl] .md-typeset .admonition.bug,[dir=rtl] .md-typeset details.bug{border-right-color:#f50057}.md-typeset .admonition.bug>.admonition-title,.md-typeset .admonition.bug>summary,.md-typeset details.bug>.admonition-title,.md-typeset details.bug>summary{border-bottom-color:rgba(245,0,87,.1);background-color:rgba(245,0,87,.1)}.md-typeset .admonition.bug>.admonition-title:before,.md-typeset .admonition.bug>summary:before,.md-typeset details.bug>.admonition-title:before,.md-typeset details.bug>summary:before{color:#f50057;content:""}.md-typeset .admonition.example,.md-typeset details.example{border-left-color:#651fff}[dir=rtl] .md-typeset .admonition.example,[dir=rtl] .md-typeset details.example{border-right-color:#651fff}.md-typeset .admonition.example>.admonition-title,.md-typeset .admonition.example>summary,.md-typeset details.example>.admonition-title,.md-typeset details.example>summary{border-bottom-color:rgba(101,31,255,.1);background-color:rgba(101,31,255,.1)}.md-typeset .admonition.example>.admonition-title:before,.md-typeset .admonition.example>summary:before,.md-typeset details.example>.admonition-title:before,.md-typeset details.example>summary:before{color:#651fff;content:""}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-left-color:#9e9e9e}[dir=rtl] .md-typeset .admonition.cite,[dir=rtl] .md-typeset .admonition.quote,[dir=rtl] .md-typeset details.cite,[dir=rtl] .md-typeset details.quote{border-right-color:#9e9e9e}.md-typeset .admonition.cite>.admonition-title,.md-typeset .admonition.cite>summary,.md-typeset .admonition.quote>.admonition-title,.md-typeset .admonition.quote>summary,.md-typeset details.cite>.admonition-title,.md-typeset details.cite>summary,.md-typeset details.quote>.admonition-title,.md-typeset details.quote>summary{border-bottom-color:hsla(0,0%,62%,.1);background-color:hsla(0,0%,62%,.1)}.md-typeset .admonition.cite>.admonition-title:before,.md-typeset .admonition.cite>summary:before,.md-typeset .admonition.quote>.admonition-title:before,.md-typeset .admonition.quote>summary:before,.md-typeset details.cite>.admonition-title:before,.md-typeset details.cite>summary:before,.md-typeset details.quote>.admonition-title:before,.md-typeset details.quote>summary:before{color:#9e9e9e;content:""}.codehilite .o,.codehilite .ow,.md-typeset .highlight .o,.md-typeset .highlight .ow{color:inherit}.codehilite .ge,.md-typeset .highlight .ge{color:#000}.codehilite .gr,.md-typeset .highlight .gr{color:#a00}.codehilite .gh,.md-typeset .highlight .gh{color:#999}.codehilite .go,.md-typeset .highlight .go{color:#888}.codehilite .gp,.md-typeset .highlight .gp{color:#555}.codehilite .gs,.md-typeset .highlight .gs{color:inherit}.codehilite .gu,.md-typeset .highlight .gu{color:#aaa}.codehilite .gt,.md-typeset .highlight .gt{color:#a00}.codehilite .gd,.md-typeset .highlight .gd{background-color:#fdd}.codehilite .gi,.md-typeset .highlight .gi{background-color:#dfd}.codehilite .k,.md-typeset .highlight .k{color:#3b78e7}.codehilite .kc,.md-typeset .highlight .kc{color:#a71d5d}.codehilite .kd,.codehilite .kn,.md-typeset .highlight .kd,.md-typeset .highlight .kn{color:#3b78e7}.codehilite .kp,.md-typeset .highlight .kp{color:#a71d5d}.codehilite .kr,.codehilite .kt,.md-typeset .highlight .kr,.md-typeset .highlight .kt{color:#3e61a2}.codehilite .c,.codehilite .cm,.md-typeset .highlight .c,.md-typeset .highlight .cm{color:#999}.codehilite .cp,.md-typeset .highlight .cp{color:#666}.codehilite .c1,.codehilite .ch,.codehilite .cs,.md-typeset .highlight .c1,.md-typeset .highlight .ch,.md-typeset .highlight .cs{color:#999}.codehilite .na,.codehilite .nb,.md-typeset .highlight .na,.md-typeset .highlight .nb{color:#c2185b}.codehilite .bp,.md-typeset .highlight .bp{color:#3e61a2}.codehilite .nc,.md-typeset .highlight .nc{color:#c2185b}.codehilite .no,.md-typeset .highlight .no{color:#3e61a2}.codehilite .nd,.codehilite .ni,.md-typeset .highlight .nd,.md-typeset .highlight .ni{color:#666}.codehilite .ne,.codehilite .nf,.md-typeset .highlight .ne,.md-typeset .highlight .nf{color:#c2185b}.codehilite .nl,.md-typeset .highlight .nl{color:#3b5179}.codehilite .nn,.md-typeset .highlight .nn{color:#ec407a}.codehilite .nt,.md-typeset .highlight .nt{color:#3b78e7}.codehilite .nv,.codehilite .vc,.codehilite .vg,.codehilite .vi,.md-typeset .highlight .nv,.md-typeset .highlight .vc,.md-typeset .highlight .vg,.md-typeset .highlight .vi{color:#3e61a2}.codehilite .nx,.md-typeset .highlight .nx{color:#ec407a}.codehilite .il,.codehilite .m,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.md-typeset .highlight .il,.md-typeset .highlight .m,.md-typeset .highlight .mf,.md-typeset .highlight .mh,.md-typeset .highlight .mi,.md-typeset .highlight .mo{color:#e74c3c}.codehilite .s,.codehilite .sb,.codehilite .sc,.md-typeset .highlight .s,.md-typeset .highlight .sb,.md-typeset .highlight .sc{color:#0d904f}.codehilite .sd,.md-typeset .highlight .sd{color:#999}.codehilite .s2,.md-typeset .highlight .s2{color:#0d904f}.codehilite .se,.codehilite .sh,.codehilite .si,.codehilite .sx,.md-typeset .highlight .se,.md-typeset .highlight .sh,.md-typeset .highlight .si,.md-typeset .highlight .sx{color:#183691}.codehilite .sr,.md-typeset .highlight .sr{color:#009926}.codehilite .s1,.codehilite .ss,.md-typeset .highlight .s1,.md-typeset .highlight .ss{color:#0d904f}.codehilite .err,.md-typeset .highlight .err{color:#a61717}.codehilite .w,.md-typeset .highlight .w{color:transparent}.codehilite .hll,.md-typeset .highlight .hll{display:block;margin:0 -.6rem;padding:0 .6rem;background-color:rgba(255,235,59,.5)}.md-typeset .codehilite,.md-typeset .highlight{position:relative;margin:1em 0;padding:0;border-radius:.1rem;background-color:hsla(0,0%,92.5%,.5);color:#37474f;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset .codehilite code,.md-typeset .codehilite pre,.md-typeset .highlight code,.md-typeset .highlight pre{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;overflow:auto;vertical-align:top}.md-typeset .codehilite code::-webkit-scrollbar,.md-typeset .codehilite pre::-webkit-scrollbar,.md-typeset .highlight code::-webkit-scrollbar,.md-typeset .highlight pre::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset .codehilite code::-webkit-scrollbar-thumb,.md-typeset .codehilite pre::-webkit-scrollbar-thumb,.md-typeset .highlight code::-webkit-scrollbar-thumb,.md-typeset .highlight pre::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset .codehilite code::-webkit-scrollbar-thumb:hover,.md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,.md-typeset .highlight code::-webkit-scrollbar-thumb:hover,.md-typeset .highlight pre::-webkit-scrollbar-thumb:hover{background-color:#00e290}.md-typeset pre.codehilite,.md-typeset pre.highlight{overflow:visible}.md-typeset pre.codehilite code,.md-typeset pre.highlight code{display:block;padding:.525rem .6rem;overflow:auto}.md-typeset .codehilitetable,.md-typeset .highlighttable{display:block;margin:1em 0;border-radius:.2em;font-size:.8rem;overflow:hidden}.md-typeset .codehilitetable tbody,.md-typeset .codehilitetable td,.md-typeset .highlighttable tbody,.md-typeset .highlighttable td{display:block;padding:0}.md-typeset .codehilitetable tr,.md-typeset .highlighttable tr{display:flex}.md-typeset .codehilitetable .codehilite,.md-typeset .codehilitetable .highlight,.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .codehilite,.md-typeset .highlighttable .highlight,.md-typeset .highlighttable .linenodiv{margin:0;border-radius:0}.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .linenodiv{padding:.525rem .6rem}.md-typeset .codehilitetable .linenos,.md-typeset .highlighttable .linenos{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.26);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset .codehilitetable .linenos pre,.md-typeset .highlighttable .linenos pre{margin:0;padding:0;background-color:transparent;color:inherit;text-align:right}.md-typeset .codehilitetable .code,.md-typeset .highlighttable .code{flex:1;overflow:hidden}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{box-shadow:none}.md-typeset [id^="fnref:"]{display:inline-block}.md-typeset [id^="fnref:"]:target{margin-top:-3.8rem;padding-top:3.8rem;pointer-events:none}.md-typeset [id^="fn:"]:before{display:none;height:0;content:""}.md-typeset [id^="fn:"]:target:before{display:block;margin-top:-3.5rem;padding-top:3.5rem;pointer-events:none}.md-typeset .footnote{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{transition:color .25s}.md-typeset .footnote li:target{color:rgba(0,0,0,.87)}.md-typeset .footnote li :first-child{margin-top:0}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}.md-typeset .footnote li:hover .footnote-backref:hover,.md-typeset .footnote li:target .footnote-backref{color:#00e290}.md-typeset .footnote-ref{display:inline-block;pointer-events:auto}.md-typeset .footnote-ref:before{display:inline;margin:0 .2em;border-left:.05rem solid rgba(0,0,0,.26);font-size:1.25em;content:"";vertical-align:-.25rem}.md-typeset .footnote-backref{display:inline-block;-webkit-transform:translateX(.25rem);transform:translateX(.25rem);transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s,-webkit-transform .25s .125s;color:rgba(0,0,0,.26);font-size:0;opacity:0;vertical-align:text-bottom}[dir=rtl] .md-typeset .footnote-backref{-webkit-transform:translateX(-.25rem);transform:translateX(-.25rem)}.md-typeset .footnote-backref:before{display:inline-block;font-size:.8rem;content:"\E31B"}[dir=rtl] .md-typeset .footnote-backref:before{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;-webkit-transform:translateY(.25rem);transform:translateY(.25rem);transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s,-webkit-transform .25s .25s;opacity:0}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}html body .md-typeset .headerlink{color:rgba(0,0,0,.26)}.md-typeset h1[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h1[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink,.md-typeset h1[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink:hover,.md-typeset h1[id]:target .headerlink{color:#00e290}.md-typeset h2[id]:before{display:block;margin-top:-8px;padding-top:8px;content:""}.md-typeset h2[id]:target:before{margin-top:-3.4rem;padding-top:3.4rem}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink,.md-typeset h2[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink:hover,.md-typeset h2[id]:target .headerlink{color:#00e290}.md-typeset h3[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h3[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink,.md-typeset h3[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink:hover,.md-typeset h3[id]:target .headerlink{color:#00e290}.md-typeset h4[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h4[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink,.md-typeset h4[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink:hover,.md-typeset h4[id]:target .headerlink{color:#00e290}.md-typeset h5[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h5[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink,.md-typeset h5[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink:hover,.md-typeset h5[id]:target .headerlink{color:#00e290}.md-typeset h6[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h6[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink,.md-typeset h6[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink:hover,.md-typeset h6[id]:target .headerlink{color:#00e290}.md-typeset .MJXc-display{margin:.75em 0;padding:.75em 0;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset .MathJax_CHTML{outline:0}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:#fdd;box-shadow:.25em 0 0 #fdd,-.25em 0 0 #fdd}.md-typeset ins.critic{background-color:#dfd;box-shadow:.25em 0 0 #dfd,-.25em 0 0 #dfd}.md-typeset .critic.comment{background-color:hsla(0,0%,92.5%,.5);color:#37474f;box-shadow:.25em 0 0 hsla(0,0%,92.5%,.5),-.25em 0 0 hsla(0,0%,92.5%,.5)}.md-typeset .critic.comment:before{padding-right:.125em;color:rgba(0,0,0,.26);content:"\E0B7";vertical-align:-.125em}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;box-shadow:none}.md-typeset .critic.block :first-child{margin-top:.5em}.md-typeset .critic.block :last-child{margin-bottom:.5em}.md-typeset details{display:block;padding-top:0}.md-typeset details[open]>summary:after{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.md-typeset details:not([open]){padding-bottom:0}.md-typeset details:not([open])>summary{border-bottom:none}.md-typeset details summary{padding-right:2rem}[dir=rtl] .md-typeset details summary{padding-left:2rem}.no-details .md-typeset details:not([open])>*{display:none}.no-details .md-typeset details:not([open]) summary{display:block}.md-typeset summary{display:block;outline:none;cursor:pointer}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset summary:after{position:absolute;top:.4rem;right:.6rem;color:rgba(0,0,0,.26);font-size:1rem;content:"\E313"}[dir=rtl] .md-typeset summary:after{right:auto;left:.6rem}.md-typeset .emojione{width:1rem;vertical-align:text-top}.md-typeset code.codehilite,.md-typeset code.highlight{margin:0 .29412em;padding:.07353em 0}.md-typeset .superfences-content{display:none;order:99;width:100%;background-color:#fff}.md-typeset .superfences-content>*{margin:0;border-radius:0}.md-typeset .superfences-tabs{display:flex;position:relative;flex-wrap:wrap;margin:1em 0;border:.05rem solid rgba(0,0,0,.07);border-radius:.2em}.md-typeset .superfences-tabs>input{display:none}.md-typeset .superfences-tabs>input:checked+label{font-weight:700}.md-typeset .superfences-tabs>input:checked+label+.superfences-content{display:block}.md-typeset .superfences-tabs>label{width:auto;padding:.6rem;transition:color .125s;font-size:.64rem;cursor:pointer}html .md-typeset .superfences-tabs>label:hover{color:#00e290}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control .task-list-indicator:before{position:absolute;top:.15em;left:-1.25em;color:rgba(0,0,0,.26);font-size:1.25em;content:"\E835";vertical-align:-.25em}[dir=rtl] .md-typeset .task-list-control .task-list-indicator:before{right:-1.25em;left:auto}.md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator:before{content:"\E834"}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}@media print{.md-typeset a:after{color:rgba(0,0,0,.54);content:" [" attr(href) "]"}.md-typeset code,.md-typeset pre{white-space:pre-wrap}.md-typeset code{box-shadow:none;-webkit-box-decoration-break:initial;box-decoration-break:slice}.md-clipboard,.md-content__icon,.md-footer,.md-header,.md-sidebar,.md-tabs,.md-typeset .headerlink{display:none}}@media only screen and (max-width:44.9375em){.md-typeset pre{margin:1em -.8rem;border-radius:0}.md-typeset pre>code{padding:.525rem .8rem}.md-footer-nav__link--prev .md-footer-nav__title{display:none}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}.codehilite .hll,.md-typeset .highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.codehilite,.md-typeset>.highlight{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilite code,.md-typeset>.codehilite pre,.md-typeset>.highlight code,.md-typeset>.highlight pre{padding:.525rem .8rem}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilitetable .codehilite>code,.md-typeset>.codehilitetable .codehilite>pre,.md-typeset>.codehilitetable .highlight>code,.md-typeset>.codehilitetable .highlight>pre,.md-typeset>.codehilitetable .linenodiv,.md-typeset>.highlighttable .codehilite>code,.md-typeset>.highlighttable .codehilite>pre,.md-typeset>.highlighttable .highlight>code,.md-typeset>.highlighttable .highlight>pre,.md-typeset>.highlighttable .linenodiv{padding:.5rem .8rem}.md-typeset>p>.MJXc-display{margin:.75em -.8rem;padding:.25em .8rem}.md-typeset>.superfences-tabs{margin:1em -.8rem;border:0;border-top:.05rem solid rgba(0,0,0,.07);border-radius:0}.md-typeset>.superfences-tabs code,.md-typeset>.superfences-tabs pre{padding:.525rem .8rem}}@media only screen and (min-width:100em){html{font-size:137.5%}}@media only screen and (min-width:125em){html{font-size:150%}}@media only screen and (max-width:59.9375em){body[data-md-state=lock]{overflow:hidden}.ios body[data-md-state=lock] .md-container{display:none}html .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}html .md-nav__link[for=__toc]:after{color:inherit;content:"\E8DE"}html .md-nav__link[for=__toc]+.md-nav__link{display:none}html .md-nav__link[for=__toc]~.md-nav{display:flex}html [dir=rtl] .md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav__source{display:block;padding:0 .2rem;background-color:rgba(69,0,203,.9675);color:#fff}.md-search__overlay{position:absolute;top:.2rem;left:.2rem;width:1.8rem;height:1.8rem;-webkit-transform-origin:center;transform-origin:center;transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:transform .3s .1s,opacity .2s .2s;transition:transform .3s .1s,opacity .2s .2s,-webkit-transform .3s .1s;border-radius:1rem;background-color:#fff;overflow:hidden;pointer-events:none}[dir=rtl] .md-search__overlay{right:.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{transition:opacity .1s,-webkit-transform .4s;transition:transform .4s,opacity .1s;transition:transform .4s,opacity .1s,-webkit-transform .4s;opacity:1}.md-search__inner{position:fixed;top:0;left:100%;width:100%;height:100%;-webkit-transform:translateX(5%);transform:translateX(5%);transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;opacity:0;z-index:2}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;-webkit-transform:translateX(0);transform:translateX(0);transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;opacity:1}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;-webkit-transform:translateX(-5%);transform:translateX(-5%)}.md-search__input{width:100%;height:2.4rem;font-size:.9rem}.md-search__icon[for=__search]{top:.6rem;left:.8rem}.md-search__icon[for=__search][for=__search]:before{content:"\E5C4"}[dir=rtl] .md-search__icon[for=__search][for=__search]:before{content:"\E5C8"}.md-search__icon[type=reset]{top:.6rem;right:.8rem}.md-search__output{top:2.4rem;bottom:0}.md-search-result__article--document:before{display:none}}@media only screen and (max-width:76.1875em){[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-header-nav__button.md-icon--home,.md-header-nav__button.md-logo{display:none}.md-hero__inner{margin-top:2.4rem;margin-bottom:1.2rem}.md-nav{background-color:#fff}.md-nav--primary,.md-nav--primary .md-nav{display:flex;position:absolute;top:0;right:0;left:0;flex-direction:column;height:100%;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}html .md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-weight:400;line-height:2.4rem;white-space:nowrap;cursor:pointer}html .md-nav--primary .md-nav__title:before{display:block;position:absolute;top:.2rem;left:.2rem;width:2rem;height:2rem;color:rgba(0,0,0,.54)}html .md-nav--primary .md-nav__title~.md-nav__list{background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07)}html .md-nav--primary .md-nav__title~.md-nav__list>.md-nav__item:first-child{border-top:0}html .md-nav--primary .md-nav__title--site{position:relative;background-color:#5700ff;color:#fff}html .md-nav--primary .md-nav__title--site .md-nav__button{display:block;position:absolute;top:.2rem;left:.2rem;width:3.2rem;height:3.2rem;font-size:2.4rem}html .md-nav--primary .md-nav__title--site:before{display:none}html [dir=rtl] .md-nav--primary .md-nav__title--site .md-nav__button,html [dir=rtl] .md-nav--primary .md-nav__title:before{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1;overflow-y:auto}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid rgba(0,0,0,.07)}[dir=rtl] .md-nav--primary .md-nav__item{padding:0}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E315"}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E314"}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link:after{position:absolute;top:50%;right:.6rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link:after{right:auto;left:.6rem}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:transparent}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav__toggle~.md-nav{display:flex;-webkit-transform:translateX(100%);transform:translateX(100%);transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);opacity:0}[dir=rtl] .md-nav__toggle~.md-nav{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.no-csstransforms3d .md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1}.no-csstransforms3d .md-nav__toggle:checked~.md-nav{display:flex}.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;width:12.1rem;height:100%;-webkit-transform:translateX(0);transform:translateX(0);transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);background-color:#fff;z-index:3}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}.no-csstransforms3d .md-sidebar--primary{display:none}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);-webkit-transform:translateX(12.1rem);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{-webkit-transform:translateX(-12.1rem);transform:translateX(-12.1rem)}.no-csstransforms3d [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{display:block}.md-sidebar--primary .md-sidebar__scrollwrap{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;margin:0}.md-tabs{display:none}}@media only screen and (min-width:60em){.md-content{margin-right:12.1rem}[dir=rtl] .md-content{margin-right:0;margin-left:12.1rem}.md-header-nav__button.md-icon--search{display:none}.md-header-nav__source{display:block;width:11.5rem;max-width:11.5rem;padding-right:.6rem}[dir=rtl] .md-header-nav__source{padding-right:0;padding-left:.6rem}.md-search{padding:.2rem}.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);cursor:pointer}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:100%;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-search__inner{position:relative;width:11.5rem;margin-right:1rem;padding:.1rem 0;float:right;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{margin-right:0;margin-left:1rem;float:left}.md-search__form,.md-search__input{border-radius:.1rem}.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);background-color:rgba(0,0,0,.26);color:inherit;font-size:.8rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:inherit}.md-search__input::-webkit-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{border-radius:.1rem .1rem 0 0;background-color:#fff;color:rgba(0,0,0,.87);text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:rgba(0,0,0,.54)}.md-search__output{top:1.9rem;transition:opacity .4s;opacity:0}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}.md-search__scrollwrap{max-height:0}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00e290}.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}.md-sidebar--secondary{display:block;margin-left:100%;-webkit-transform:translate(-100%);transform:translate(-100%)}[dir=rtl] .md-sidebar--secondary{margin-right:100%;margin-left:0;-webkit-transform:translate(100%);transform:translate(100%)}}@media only screen and (min-width:76.25em){.md-content{margin-left:12.1rem}[dir=rtl] .md-content{margin-right:12.1rem}.md-content__inner{margin-right:1.2rem;margin-left:1.2rem}.md-header-nav__button.md-icon--menu{display:none}.md-nav[data-md-state=animate]{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav__toggle~.md-nav{max-height:0;overflow:hidden}.no-js .md-nav__toggle~.md-nav{display:none}.md-nav[data-md-state=expand],.md-nav__toggle:checked~.md-nav{max-height:100%}.no-js .md-nav[data-md-state=expand],.no-js .md-nav__toggle:checked~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--nested>.md-nav__link:after{display:inline-block;-webkit-transform-origin:.45em .45em;transform-origin:.45em .45em;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;vertical-align:-.125em}.js .md-nav__item--nested>.md-nav__link:after{transition:-webkit-transform .4s;transition:transform .4s;transition:transform .4s,-webkit-transform .4s}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link:after{-webkit-transform:rotateX(180deg);transform:rotateX(180deg)}.md-search__inner{margin-right:1.4rem}[dir=rtl] .md-search__inner{margin-left:1.4rem}.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}.md-sidebar--secondary{margin-left:61rem}[dir=rtl] .md-sidebar--secondary{margin-right:61rem;margin-left:0}.md-tabs~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary .md-nav__title{display:block;padding:0}.md-tabs--active~.md-main .md-nav--primary .md-nav__title--site{display:none}.no-js .md-tabs--active~.md-main .md-nav--primary .md-nav{display:block}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{display:none;font-size:.7rem;overflow:auto;visibility:visible}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested>.md-nav__link{display:none}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--active{display:block}.md-tabs--active~.md-main .md-nav[data-md-level="1"]{max-height:none;overflow:visible}.md-tabs--active~.md-main .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:0}.md-tabs--active~.md-main .md-nav[data-md-level="1"] .md-nav .md-nav__title{display:none}}@media only screen and (min-width:45em){.md-footer-nav__link{width:50%}.md-footer-copyright{max-width:75%;float:left}[dir=rtl] .md-footer-copyright{float:right}.md-footer-social{padding:.6rem 0;float:right}[dir=rtl] .md-footer-social{float:left}}@media only screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(45);transform:scale(45)}}@media only screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(60);transform:scale(60)}}@media only screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(75);transform:scale(75)}}@media only screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}} \ No newline at end of file diff --git a/docs/theme/assets/stylesheets/extra.css b/docs/theme/assets/stylesheets/extra.css new file mode 100644 index 000000000..788114b34 --- /dev/null +++ b/docs/theme/assets/stylesheets/extra.css @@ -0,0 +1,15 @@ +.help-sidetab { + position: fixed; + top: 70%; + right: -71px; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); + color: white; + background-color: #00e290; + transform: rotate(270deg); + padding: 5px 20px 5px 20px; + border-radius : 10px 10px 0px 0px; + cursor: pointer; + font-size: medium; +} \ No newline at end of file diff --git a/docs/theme/base.html b/docs/theme/base.html new file mode 100644 index 000000000..8dc736895 --- /dev/null +++ b/docs/theme/base.html @@ -0,0 +1,220 @@ +{% import "partials/language.html" as lang with context %} +{% set feature = config.theme.feature %} +{% set palette = config.theme.palette %} +{% set font = config.theme.font %} + + + + {% block site_meta %} + + + + {% if page and page.meta and page.meta.description %} + + {% elif config.site_description %} + + {% endif %} + {% if page and page.meta and page.meta.redirect %} + + + + + {% elif page.canonical_url %} + + {% endif %} + {% if page and page.meta and page.meta.author %} + + {% elif config.site_author %} + + {% endif %} + {% for key in [ + "clipboard.copy", + "clipboard.copied", + "search.language", + "search.pipeline.stopwords", + "search.pipeline.trimmer", + "search.result.none", + "search.result.one", + "search.result.other", + "search.tokenizer" + ] %} + + {% endfor %} + + + {% endblock %} + {% block htmltitle %} + {% if page and page.meta and page.meta.title %} + {{ page.meta.title }} + {% elif page and page.title and not page.is_homepage %} + {{ page.title }} - {{ config.site_name }} + {% else %} + {{ config.site_name }} + {% endif %} + {% endblock %} + {% block styles %} + + {% if palette.primary or palette.accent %} + + {% endif %} + {% if palette.primary %} + {% import "partials/palette.html" as map %} + {% set primary = map.primary( + palette.primary | replace(" ", "-") | lower + ) %} + + {% endif %} + {% endblock %} + {% block libs %} + + {% endblock %} + {% block fonts %} + {% if font != false %} + + + + {% endif %} + {% endblock %} + + {% if config.extra.manifest %} + + {% endif %} + {% for path in config["extra_css"] %} + + {% endfor %} + {% block analytics %} + {% if config.google_analytics %} + {% include "partials/integrations/analytics.html" %} + {% endif %} + {% endblock %} + {% block extrahead %}{% endblock %} + + {% if palette.primary or palette.accent %} + {% set primary = palette.primary | replace(" ", "-") | lower %} + {% set accent = palette.accent | replace(" ", "-") | lower %} + + {% else %} + + {% endif %} + + + {% set platform = config.extra.repo_icon or config.repo_url %} + {% if "github" in platform %} + {% include "assets/images/icons/github.f0b8504a.svg" %} + {% elif "gitlab" in platform %} + {% include "assets/images/icons/gitlab.6dd19c00.svg" %} + {% elif "bitbucket" in platform %} + {% include "assets/images/icons/bitbucket.1b09e088.svg" %} + {% endif %} + + + + + + {% if page.toc | first is defined %} + + {{ lang.t('skip.link.title') }} + + {% endif %} + {% block header %} + {% include "partials/header.html" %} + {% endblock %} +
+ {% block hero %} + {% if page and page.meta and page.meta.hero %} + {% include "partials/hero.html" with context %} + {% endif %} + {% endblock %} + {% if feature.tabs %} + {% include "partials/tabs.html" %} + {% endif %} +
+
+ {% block site_nav %} + {% if nav %} +
+
+
+ {% include "partials/nav.html" %} +
+
+
+ {% endif %} + {% if page.toc %} +
+
+
+ {% include "partials/toc.html" %} +
+
+
+ {% endif %} + {% endblock %} +
+
+ {% block content %} + {% if page.edit_url %} + + {% endif %} + {% if not "\x3ch1" in page.content %} +

{{ page.title | default(config.site_name, true)}}

+ {% endif %} + {{ page.content }} + {% block source %} + {% if page and page.meta and page.meta.source %} +

{{ lang.t("meta.source") }}

+ {% set repo = config.repo_url %} + {% if repo | last == "/" %} + {% set repo = repo[:-1] %} + {% endif %} + {% set path = page.meta.path | default([""]) %} + {% set file = page.meta.source %} + + {{ file }} + + {% endif %} + {% endblock %} + {% endblock %} + {% block disqus %} + {% include "partials/integrations/disqus.html" %} + {% endblock %} +
+
+
+
+ {% block footer %} + {% include "partials/footer.html" %} + {% endblock %} +
+ {% block scripts %} + + {% if lang.t("search.language") != "en" %} + {% set languages = lang.t("search.language").split(",") %} + {% if languages | length and languages[0] != "" %} + {% set path = "assets/javascripts/lunr/" %} + + {% for language in languages | map("trim") %} + {% if language != "en" %} + {% if language == "ja" %} + + {% endif %} + {% if language in ("da", "de", "es", "fi", "fr", "hu", "it", "ja", "nl", "no", "pt", "ro", "ru", "sv", "th", "tr") %} + + {% endif %} + {% endif %} + {% endfor %} + {% if languages | length > 1 %} + + {% endif %} + {% endif %} + {% endif %} + + {% for path in config["extra_javascript"] %} + + {% endfor %} + {% endblock %} + + diff --git a/docs/theme/partials/footer.html b/docs/theme/partials/footer.html new file mode 100644 index 000000000..5200de545 --- /dev/null +++ b/docs/theme/partials/footer.html @@ -0,0 +1,80 @@ +{% import "partials/language.html" as lang with context %} + +
+ +
diff --git a/docs/theme/partials/header.html b/docs/theme/partials/header.html new file mode 100644 index 000000000..a063f3ad9 --- /dev/null +++ b/docs/theme/partials/header.html @@ -0,0 +1,87 @@ + + + +
+ + + +
diff --git a/docs/theme/partials/nav.html b/docs/theme/partials/nav.html new file mode 100644 index 000000000..d68eb9d52 --- /dev/null +++ b/docs/theme/partials/nav.html @@ -0,0 +1,46 @@ + + + + diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..95e6b1c59 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,94 @@ + +# Project information +site_name: Quorum +site_url: https://goquorum.readthedocs.io/ +site_description: A permissioned implementation of Ethereum supporting data privacy +site_author: Quorum +copyright: Quorum 2019 + +# Repository +repo_name: quorum +repo_url: https://github.com/jpmorganchase/quorum + +nav: + - Getting Started: + - Setup Overview & Quickstart: Getting Started/Setup Overview & Quickstart.md + - Getting Started From Scratch: Getting Started/Getting-Started-From-Scratch.md + - Quorum Examples: + - Overview: Getting Started/Quorum-Examples.md + - 7 Nodes Example: Getting Started/7Nodes.md + - Running Quorum: Getting Started/running.md + - Consensus: + - Consensus: Consensus/Consensus.md + - Raft: Consensus/raft.md + - Istanbul: Consensus/istanbul-rpc-api.md + - Transaction Processing: Transaction Processing/Transaction Processing.md + - Security: + - Security & Permissioning: Security/Security & Permissioning.md + - Privacy: + - Tessera: + - What is Tessera: Privacy/Tessera/Tessera.md + - How Tessera works: Privacy/Tessera/How Tessera Works.md + - Configuration: + - Overview: Privacy/Tessera/Configuration/Configuration Overview.md + - Keys Config: Privacy/Tessera/Configuration/Keys.md + - TLS Config: Privacy/Tessera/Configuration/TLS.md + - Using CLI to override config: Privacy/Tessera/Configuration/Using CLI to override config.md + - Sample Configuration: Privacy/Tessera/Configuration/Sample Configuration.md + - Tessera Services: + - Transaction Manager: Privacy/Tessera/Tessera Services/Transaction Manager.md + - Enclave: Privacy/Tessera/Tessera Services/Enclave.md + - Keys: + - Key Generation: Privacy/Tessera/Tessera Services/Keys/Keys.md + - Setting up Hashicorp Vault: Privacy/Tessera/Tessera Services/Keys/Setting up a Hashicorp Vault.md + - Setting up Azure Key Vault: Privacy/Tessera/Tessera Services/Keys/Setting up an Azure Key Vault.md + - Usage: + - Interfaces & API: Privacy/Tessera/Usage/Interface & API.md + - Admin Usage: Privacy/Tessera/Usage/Admin Usage.md + - Monitoring: Privacy/Tessera/Usage/Monitoring.md + - Migration from Constellation: Privacy/Tessera/Migration from Constellation.md + - Constellation: + - What is Constellation: Privacy/Constellation/Constellation.md + - How it works: Privacy/Constellation/How constellation works.md + - Sample Configuration: Privacy/Constellation/Sample Configuration.md + - Running Constellation: Privacy/Constellation/Installation & Running.md + +theme: + name: 'material' + custom_dir: 'docs/theme' + font: + text: 'Roboto' + code: 'Roboto Mono' + language: 'en' + logo: 'images/logo.png' + +extra_css: + - 'theme/assets/stylesheets/extra.css' + +markdown_extensions: + - toc: + permalink: true + toc_depth: 3 + - codehilite + - admonition + - footnotes + - def_list + - abbr + - pymdownx.arithmatex + - pymdownx.betterem: + smart_enable: all + - pymdownx.keys + - pymdownx.details + - pymdownx.emoji + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + - meta + - smarty + +plugins: + - search