diff --git a/.gitignore b/.gitignore index 322e8b3..7d48497 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ /.GOPATH /bin /vendor +/test/test1 +/test/test2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e4319a..1c16736 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,11 +10,12 @@ variables: # repository in /go/src/gitlab.com/namespace/project # Thus, making a symbolic link corrects this. before_script: - - apt-get update -qq && apt-get install -y -qq libdb-dev # This is hopefully temporary until we completely remove BerkeleyDB. + - apt-get update -qq && apt-get install -y -qq libdb-dev libpthread-stubs0-dev # This is hopefully temporary until we completely remove BerkeleyDB. - mkdir -p $GOPATH/src/$(dirname $REPO_NAME) - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME - cd $GOPATH/src/$REPO_NAME - go get ./... + - make setup stages: - build @@ -25,12 +26,20 @@ format: script: - go fmt $(go list ./... | grep -v /vendor/) - go vet $(go list ./... | grep -v /vendor/) - - go test -race $(go list ./... | grep -v /vendor/) + - make test compile: stage: build script: - - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/crux + - make build artifacts: paths: - crux + +code_coverage: + stage: test + script: + - rm -f $GOPATH/cover/*.out $GOPATH/cover/all.merged + - mkdir -p $GOPATH/cover + - make cover + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cffedfa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: go + +go: + - "1.10.x" + - tip + +env: + REPO_NAME: github.com/blk-io/crux + +before_install: + - sudo apt-get update -qq && sudo apt-get install -y -qq libdb-dev libpthread-stubs0-dev # This is hopefully temporary until we completely remove BerkeleyDB. + - mkdir -p $GOPATH/src/$(dirname $REPO_NAME) + - ln -svf $TRAVIS_BUILD_DIR $GOPATH/src/$REPO_NAME + - cd $GOPATH/src/$REPO_NAME + - curl -OL https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip + - unzip protoc-3.5.1-linux-x86_64.zip -d protoc3 + - sudo mv protoc3/bin/* /usr/local/bin/ + - sudo mv protoc3/include/* /usr/local/include/ + - rm -r protoc3 + - rm protoc-3.5.1-linux-x86_64.zip + - go get -u github.com/golang/protobuf/protoc-gen-go + - go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway + - make setup + +script: + - make build + - make test + - make cover + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..30dcc68 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ + ## 1.0.3 - 2018-10-17 + ### Added + - Network interface paramater to configuration + ### Changed + - Fix formatting of entire project using `go fmt` + - Fix "apk WARNING Ignoring APKINDEX" + - Fix sending payload to multiple recipients + + ## 1.0.2 - 2018-09-06 + ### Added + - Delete and Resend API from Chimera included. + + ## 1.0.1 - 2018-08-22 + ### Changed + - Fix issue with config file load + + ## 1.0.0 - 2018-07-05 + ### Added + - Crux, a secure enclave for Quorum written in Golang + - Protobuf and gRPC support. + - TLS support + - Docker images + \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock index 7f0a838..79cf469 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,19 +2,60 @@ [[projects]] + digest = "1:86840754a6a45d993a441d23094f501eb0cb0b12fe71f4c6cab2f3a826cb8725" + name = "github.com/blk-io/chimera-api" + packages = ["chimera"] + pruneopts = "T" + revision = "ebd4db90873296427420c2fe2acec18c127b401d" + +[[projects]] + digest = "1:7fc160b460a6fc506b37fcca68332464c3f2cd57b6e3f111f26c5bbfd2d5518e" name = "github.com/fsnotify/fsnotify" packages = ["."] + pruneopts = "T" revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" version = "v1.4.7" +[[projects]] + digest = "1:832e17df5ff8bbe0e0693d2fb46c5e53f96c662ee804049ce3ab6557df74e3ab" + name = "github.com/golang/protobuf" + packages = [ + "jsonpb", + "proto", + "protoc-gen-go/descriptor", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/struct", + "ptypes/timestamp", + ] + pruneopts = "T" + revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" + version = "v1.1.0" + [[projects]] branch = "master" + digest = "1:cf5bb7d7c59d8313289e5b756e24462cacd958d1e6db3bdbe1c800c677ad0f94" name = "github.com/golang/snappy" packages = ["."] + pruneopts = "T" revision = "553a641470496b2327abcac10b36396bd98e45c9" +[[projects]] + digest = "1:69cd81163a00bb8405194d47b8be19283744779b6104f2d6b3735e2a01cdb6fa" + name = "github.com/grpc-ecosystem/grpc-gateway" + packages = [ + "runtime", + "runtime/internal", + "utilities", + ] + pruneopts = "T" + revision = "92583770e3f01b09a0d3e9bdf64321d8bebd48f2" + version = "v1.4.1" + [[projects]] branch = "master" + digest = "1:17a030e647213ad422df723c8ee8902328040dd74b7fc68cb016784baaf95657" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -26,17 +67,21 @@ "hcl/token", "json/parser", "json/scanner", - "json/token" + "json/token", ] + pruneopts = "T" revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" [[projects]] branch = "master" + digest = "1:e29757afd23af4c0c109594ccf0daaf8dd8e325f56ad38febbc4d13adfde0614" name = "github.com/jsimonetti/berkeleydb" packages = ["."] + pruneopts = "T" revision = "5cde5eaaf78c6510c5f64f5347244806a06ba87b" [[projects]] + digest = "1:599ec2ed1b0ab8e5b2b6d6d849c47cbbf5733d7988aee541fd1e5befe69b6095" name = "github.com/kevinburke/nacl" packages = [ ".", @@ -44,70 +89,90 @@ "onetimeauth", "randombytes", "scalarmult", - "secretbox" + "secretbox", ] + pruneopts = "T" revision = "247b7cfd826641547b7ee8b89c785901c4e94b0b" version = "0.5" [[projects]] + digest = "1:32ce5f79bec2865f25ae331a39ad67e507c9a6ed0b0da298db2c29efa8fe366f" name = "github.com/magiconair/properties" packages = ["."] + pruneopts = "T" revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" version = "v1.7.6" [[projects]] branch = "master" + digest = "1:2514da1e59c0a936d8c1e0fbf5592267a3c5893eb4555ce767bb54d149e9cf6e" name = "github.com/mitchellh/mapstructure" packages = ["."] + pruneopts = "T" revision = "00c29f56e2386353d58c599509e8dc3801b0d716" [[projects]] + digest = "1:63fc640566e87c5a5f2f3646725f308cf1a6b8e30fdba2d49941d978c3cb9b4d" name = "github.com/pelletier/go-toml" packages = ["."] + pruneopts = "T" revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" version = "v1.1.0" [[projects]] + digest = "1:047075087eff9bcda0806c636c479fae29b9ae55da320d3d961eb5d3203e2a75" name = "github.com/sirupsen/logrus" packages = ["."] + pruneopts = "T" revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" version = "v1.0.5" [[projects]] + digest = "1:ed2a3679c070c90f83aacc19d114536db7c5b739980837fbdc86cbf937493400" name = "github.com/spf13/afero" packages = [ ".", - "mem" + "mem", ] + pruneopts = "T" revision = "63644898a8da0bc22138abf860edaf5277b6102e" version = "v1.1.0" [[projects]] + digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" name = "github.com/spf13/cast" packages = ["."] + pruneopts = "T" revision = "8965335b8c7107321228e3e3702cab9832751bac" version = "v1.2.0" [[projects]] branch = "master" + digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805" name = "github.com/spf13/jwalterweatherman" packages = ["."] + pruneopts = "T" revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" [[projects]] + digest = "1:9798f8595f3bf57586a622e8e78b7b8d159ceb375b9485c7fbcd2fd686ac4c46" name = "github.com/spf13/pflag" packages = ["."] + pruneopts = "T" revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" version = "v1.0.0" [[projects]] + digest = "1:fbfebb70b35ccd17f5a91ba29f3d9dfeea149a98b44780a14efade3526c09bb3" name = "github.com/spf13/viper" packages = ["."] + pruneopts = "T" revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736" version = "v1.0.2" [[projects]] branch = "master" + digest = "1:ef0753127ee10562925146777ed5f5f41431c0ccc8fa63da1cfae678cc7b8c74" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -121,53 +186,145 @@ "leveldb/opt", "leveldb/storage", "leveldb/table", - "leveldb/util" + "leveldb/util", ] + pruneopts = "T" revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" [[projects]] branch = "master" + digest = "1:dbe2585d9a08433ff9d1951bab1df0bc8c1bbd50c9fb866f92b661d88beb5694" name = "golang.org/x/crypto" packages = [ "curve25519", "poly1305", "salsa20/salsa", "sha3", - "ssh/terminal" + "ssh/terminal", ] + pruneopts = "T" revision = "beb2a9779c3b677077c41673505f150149fce895" [[projects]] branch = "master" + digest = "1:1591a31d3ebd0f8d083ec588de4fb0b540809cf75edee9467c554cc17da0620a" + name = "golang.org/x/net" + packages = [ + "context", + "http/httpguts", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "trace", + ] + pruneopts = "T" + revision = "89e543239a64caf31d3a6865872ea120b41446df" + +[[projects]] + branch = "master" + digest = "1:0c0ea93b25f3ef36c9662a679ba5fddae56ade049701857d020e3e73ae707e4b" name = "golang.org/x/sys" packages = [ "unix", - "windows" + "windows", ] + pruneopts = "T" revision = "3b87a42e500a6dc65dae1a55d0b641295971163e" [[projects]] + digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a" name = "golang.org/x/text" packages = [ + "collate", + "collate/build", + "internal/colltab", "internal/gen", + "internal/tag", "internal/triegen", "internal/ucd", + "language", + "secure/bidirule", "transform", + "unicode/bidi", "unicode/cldr", - "unicode/norm" + "unicode/norm", + "unicode/rangetable", ] + pruneopts = "T" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] + branch = "master" + digest = "1:baedbfe72924071a9c7a4f8f12819d3b18a87743f645e5b638ba7a25865f00b1" + name = "google.golang.org/genproto" + packages = [ + "googleapis/api/annotations", + "googleapis/rpc/status", + ] + pruneopts = "T" + revision = "694d95ba50e67b2e363f3483057db5d4910c18f9" + +[[projects]] + digest = "1:cb75df728d7afe7b7a7f546c19b8fd1c5fc801bdfea88fbd1a6a4a0af5072e3a" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "channelz", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclb/grpc_lb_v1/messages", + "grpclog", + "internal", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + "transport", + ] + pruneopts = "T" + revision = "41344da2231b913fa3d983840a57a6b1b7b631a1" + version = "v1.12.0" + +[[projects]] + digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "T" revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" version = "v2.2.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "41d3681f00e64a0f64e2519e43b5843746a675772e7112a45abbb21ba87fd6bf" + input-imports = [ + "github.com/blk-io/chimera-api/chimera", + "github.com/grpc-ecosystem/grpc-gateway/runtime", + "github.com/jsimonetti/berkeleydb", + "github.com/kevinburke/nacl", + "github.com/kevinburke/nacl/box", + "github.com/kevinburke/nacl/secretbox", + "github.com/sirupsen/logrus", + "github.com/spf13/pflag", + "github.com/spf13/viper", + "github.com/syndtr/goleveldb/leveldb", + "golang.org/x/crypto/sha3", + "golang.org/x/net/context", + "google.golang.org/grpc", + "google.golang.org/grpc/credentials", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 7768a2f..5d33ed1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -55,4 +55,11 @@ [prune] go-tests = true - unused-packages = true + +[[constraint]] + name = "github.com/grpc-ecosystem/grpc-gateway" + version = "1.4.0" + +[[constraint]] + name = "github.com/blk-io/chimera-api" + revision = "ebd4db90873296427420c2fe2acec18c127b401d" diff --git a/Makefile b/Makefile index b523249..600abc3 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,8 @@ clean: $Q rm -rf bin .GOPATH test: .GOPATH/.ok + $Q ./bin/crux --url=http://127.0.0.1:9020/ --port=9020 --workdir=test/test1 --publickeys=testdata/key.pub --privatekeys=testdata/key & + $Q ./bin/crux --url=http://127.0.0.1:9025/ --port=9025 --workdir=test/test2 --publickeys=testdata/rcpt1.pub --privatekeys=testdata/rcpt1 & $Q go test $(if $V,-v) -i -race $(allpackages) # install -race libs to speed up next run ifndef CI $Q go vet $(allpackages) @@ -38,6 +40,7 @@ else $Q ( GODEBUG=cgocheck=2 go test -v -race $(allpackages); echo $$? ) | \ tee .GOPATH/test/output.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/output.txt) endif + $Q pkill crux list: .GOPATH/.ok @echo $(allpackages) @@ -109,9 +112,8 @@ Q := $(if $V,,@) .PHONY: bin/gocovmerge bin/goimports bin/gocovmerge: .GOPATH/.ok - @test -d ./vendor/github.com/wadey/gocovmerge || \ + @test -f ./bin/gocovmerge || \ { echo "Vendored gocovmerge not found, try running 'make setup'..."; exit 1; } - $Q go install $(IMPORT_PATH)/vendor/github.com/wadey/gocovmerge bin/goimports: .GOPATH/.ok @test -d ./vendor/golang.org/x/tools/cmd/goimports || \ { echo "Vendored goimports not found, try running 'make setup'..."; exit 1; } diff --git a/README.md b/README.md new file mode 100644 index 0000000..553563e --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +# Crux + +Quorum Slack +Build Status +Go Report Card + +Data privacy for Quorum. + +Crux is a secure enclave for Quorum written in Golang. + +It is a replacement for [Constellation](https://github.com/jpmorganchase/constellation/), the +secure enclave component of [Quorum](https://github.com/jpmorganchase/quorum/), written in Haskell. + +## Getting started + +### 4-node Quorum network with Crux + +The best way to start is to run the +[Quorum-Crux Docker image](https://github.com/blk-io/crux/tree/master/docker/quorum-crux). This +image runs a 4 node Quorum network using Crux as the secure enclave communicating over gRPC. + +```bash +git clone https://github.com/blk-io/crux.git +docker-compose -f docker/quorum-crux/docker-compose.yaml up +``` + +Where the node details are as follows: + +| Name | Quorum node address | Account key | Crux node key | +| ------- | ----------------------- | ------------------------------------------ | -------------------------------------------- | +| quorum1 | http://localhost:22001 | 0xed9d02e382b34818e88b88a309c7fe71e65f419d | BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= | +| quorum2 | http://localhost:22002 | 0xca843569e3427144cead5e4d5999a3d0ccf92b8e | QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc= | +| quorum3 | http://localhost:22003 | 0x0fbdc686b912d7722dc86510934589e0aaf3b55a | 1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg= | +| quorum4 | http://localhost:22004 | 0x9186eb3d20cbd1f5f992a950d808c4495153abd5 | oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8= | + +#### local docker +If you want to make changes to e.g. istanbul-start.sh then build the docker image locally: + + docker-compose -f docker-compose-local.yaml up --build + +### 2-node Crux only-network + +[2 Crux nodes example](https://github.com/blk-io/crux/tree/master/docker/crux) is simple Docker +image to just bring up 2 Crux nodes which communicate with each other. + +```bash +git clone https://github.com/blk-io/crux.git +docker-compose -f docker/crux/docker-compose.yaml up +``` + +Where the Crux node keys are the same as `quorum1` and `quorum2` above, and are listening on ports +9001 and 9002 for gRPC requests. + +### Vagrant VM + +For those of you who are unable to use Docker, you can run the +[7 Nodes Quorum example](https://github.com/blk-io/quorum-examples) which is an updated version +of JP Morgan's Quorum 7 Nodes example using Crux as the secure enclave. + +### Download the latest binary + +The latest binaries for different platforms are available on the +[release](https://github.com/blk-io/crux/releases/latest) page. + +## Generating keys + +Each Crux instance requires at least one key-pair to be associated with it. The key-pair is used +to ensure transaction privacy. Crux uses the [NaCl cryptography library](https://nacl.cr.yp.to/). + +You use the `--generate-keys` argument to generate a new key-pair with Crux: + +```bash +crux --generate-keys myKey +``` + +This will produce two files, named `myKey.key` and `myKey.pub` reflecting the private and public keys +respectively. + +## Core configuration + +At a minimum, Crux requires the following configuration parameters. This tells the Crux instance +what port it is running on and what ip address it should advertise to other peers. + +Details of at least one key-pair must be provided for the Crux node to store requests on behalf of. + +```bash +crux --url=http://127.0.0.1:9001/ --port=9001 --workdir=crux --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/ +``` + +## Build instructions + +If you'd prefer to run just a client, you can build using the below instructions and run as per +the below. + +```bash +git clone https://github.com/blk-io/crux.git +cd crux +make setup && make +./bin/crux + +Usage of ./bin/crux: + crux.config Optional config file + --alwayssendto string List of public keys for nodes to send all transactions too + --berkeleydb Use Berkeley DB for working with an existing Constellation data store [experimental] + --generate-keys string Generate a new keypair + --grpc Use gRPC server (default true) + --grpcport int The local port to listen on for JSON extensions of gRPC (default -1) + --networkinterface string The network interface to bind the server to (default "localhost") + --othernodes string "Boot nodes" to connect to to discover the network + --port int The local port to listen on (default -1) + --privatekeys string Private keys hosted by this node + --publickeys string Public keys hosted by this node + --socket string IPC socket to create for access to the Private API (default "crux.ipc") + --storage string Database storage file name (default "crux.db") + --tls Use TLS to secure HTTP communications + --tlsservercert string The server certificate to be used + --tlsserverkey string The server private key + --url string The URL to advertise to other nodes (reachable by them) + -v, --v int Verbosity level of logs (shorthand) (default 1) + --verbosity int Verbosity level of logs (default 1) + --workdir string The folder to put stuff in (default: .) (default ".") +``` + +## How does it work? + +At present, Crux performs its cryptographic operations in a manner identical to Constellation. You +can read the specifics [here](https://github.com/jpmorganchase/constellation/#how-it-works). + +The two main workflows for handling private transactions are the submission and retrieval +demonstrated below. + +### New transaction submission + +![New Transaction Sequence](./docs/new-tx.svg) + +### Existing transaction retrieval + +![Read Transaction Sequence](./docs/read-tx.svg) + +## Logical architecture + +![Logical architecture](https://github.com/blk-io/crux/blob/master/docs/quorum-architecture.png) + +## Why Crux? + +*Crux is a constellation located in the southern sky in a bright portion of the Milky Way. It is +among the most easily distinguished constellations, even though it is the smallest of all 88 +modern constellations. (Source: [Wikipedia](https://en.wikipedia.org/wiki/Crux))* + +*The critical or transitional moment or issue, a turning point.* + +## Thanks + +[@patrickmn](https://github.com/patrickmn) the original author of Constellation. Crux would not +exist were it not for his work. diff --git a/api/client.go b/api/client.go index b71ba46..f56efe3 100644 --- a/api/client.go +++ b/api/client.go @@ -1,39 +1,65 @@ package api +// SendRequest sends a new transaction to the enclave for storage and propagation to the provided +// recipients. type SendRequest struct { - Payload string `json:"payload"` - From string `json:"from"` - To []string `json:"to"` + // Payload is the transaction payload data we wish to store. + Payload string `json:"payload"` + // From is the sender node identification. + From string `json:"from"` + // To is a list of the recipient nodes that should be privy to this transaction payload. + To []string `json:"to"` } +// SendResponse is the response to the SendRequest type SendResponse struct { - Key string `json:"key"` + // Key is the key that can be used to retrieve the submitted transaction. + Key string `json:"key"` } +// ReceiveRequest type ReceiveRequest struct { - Key string `json:"key"` - To string `json:"to"` + Key string `json:"key"` + To string `json:"to"` } +// ReceiveResponse returns the raw payload associated with the ReceiveRequest. type ReceiveResponse struct { - Payload string `json:"payload"` + Payload string `json:"payload"` } +// DeleteRequest deletes the entry matching the given key from the enclave. type DeleteRequest struct { - Key string `json:"key"` + Key string `json:"key"` } +// ResendRequest is used to resend previous transactions. +// There are two types of supported request. +// 1. All transactions associated with a node, in which case the Key field should be omitted. +// 2. A specific transaction with the given key value. type ResendRequest struct { - Type string `json:"type"` - PublicKey string `json:"publicKey"` - Key string `json:"key,omitempty"` + // Type is the resend request type. It should be either "all" or "individual" depending on if + // you want to request an individual transaction, or all transactions associated with a node. + Type string `json:"type"` + PublicKey string `json:"publicKey"` + Key string `json:"key,omitempty"` } +type UpdatePartyInfo struct { + Url string `json:"url"` + Recipients map[string][]byte `json:"recipients"` + Parties map[string]bool `json:"parties"` +} + +type PartyInfoResponse struct { + Payload []byte `json:"payload"` +} type PrivateKeyBytes struct { - Bytes string `json:"bytes"` + Bytes string `json:"bytes"` } +// PrivateKey is a container for a private key. type PrivateKey struct { - Data PrivateKeyBytes `json:"data"` - Type string `json:"type"` + Data PrivateKeyBytes `json:"data"` + Type string `json:"type"` } diff --git a/api/encoding.go b/api/encoding.go index 2e3880d..d1edb9d 100644 --- a/api/encoding.go +++ b/api/encoding.go @@ -1,9 +1,9 @@ package api import ( - "github.com/kevinburke/nacl" "encoding/binary" "github.com/blk-io/crux/utils" + "github.com/kevinburke/nacl" ) func EncodePayload(ep EncryptedPayload) []byte { @@ -23,8 +23,8 @@ func EncodePayload(ep EncryptedPayload) []byte { func DecodePayload(encoded []byte) EncryptedPayload { ep := EncryptedPayload{ - Sender: new([nacl.KeySize]byte), - Nonce: new([nacl.NonceSize]byte), + Sender: new([nacl.KeySize]byte), + Nonce: new([nacl.NonceSize]byte), RecipientNonce: new([nacl.NonceSize]byte), } @@ -128,7 +128,7 @@ func writeInt(v int, dest []byte, offset int) ([]byte, int) { func confirmCapacity(dest []byte, offset, required int) []byte { length := len(dest) - if length - offset < required { + if length-offset < required { var newLength int if required > length { newLength = utils.NextPowerOf2(required) @@ -157,14 +157,14 @@ func writeSlice(src []byte, dest []byte, offset int) ([]byte, int) { func readSliceToArray(src []byte, offset int, dest []byte) int { var length int length, offset = readInt(src, offset) - offset += copy(dest, src[offset:offset + length]) + offset += copy(dest, src[offset:offset+length]) return offset } func readSlice(src []byte, offset int) ([]byte, int) { var length int length, offset = readInt(src, offset) - return src[offset:offset + length], offset + length + return src[offset : offset+length], offset + length } func writeSliceOfSlice(src [][]byte, dest []byte, offset int) ([]byte, int) { @@ -186,8 +186,8 @@ func readSliceOfSlice(src []byte, offset int) ([][]byte, int) { var length int length, offset = readInt(src, offset) result[i] = append( - result[i], src[offset:offset + length]...) + result[i], src[offset:offset+length]...) offset += length } return result, offset -} \ No newline at end of file +} diff --git a/api/encoding_test.go b/api/encoding_test.go index 8a07141..54362cf 100644 --- a/api/encoding_test.go +++ b/api/encoding_test.go @@ -1,19 +1,19 @@ package api import ( + "github.com/blk-io/crux/utils" + "github.com/kevinburke/nacl" "reflect" "testing" - "github.com/kevinburke/nacl" - "github.com/blk-io/crux/utils" ) func TestEncodePayload(t *testing.T) { epl := EncryptedPayload{ - Sender: nacl.NewKey(), - CipherText: []byte("C1ph3r T3xt"), - Nonce: nacl.NewNonce(), - RecipientBoxes: [][]byte{ []byte("B0x1"), []byte("B0x2") }, + Sender: nacl.NewKey(), + CipherText: []byte("C1ph3r T3xt"), + Nonce: nacl.NewNonce(), + RecipientBoxes: [][]byte{[]byte("B0x1"), []byte("B0x2")}, RecipientNonce: nacl.NewNonce(), } @@ -29,17 +29,17 @@ func TestEncodePayloadWithRecipients(t *testing.T) { epls := []EncryptedPayload{ { - Sender: nacl.NewKey(), - CipherText: []byte("C1ph3r T3xt1"), - Nonce: nacl.NewNonce(), - RecipientBoxes: [][]byte{ []byte("B0x1"), []byte("B0x2"), []byte("B0x3") }, + Sender: nacl.NewKey(), + CipherText: []byte("C1ph3r T3xt1"), + Nonce: nacl.NewNonce(), + RecipientBoxes: [][]byte{[]byte("B0x1"), []byte("B0x2"), []byte("B0x3")}, RecipientNonce: nacl.NewNonce(), }, { - Sender: nacl.NewKey(), - CipherText: []byte("C1ph3r T3xt2"), - Nonce: nacl.NewNonce(), - RecipientBoxes: [][]byte{ []byte("B0x1") }, + Sender: nacl.NewKey(), + CipherText: []byte("C1ph3r T3xt2"), + Nonce: nacl.NewNonce(), + RecipientBoxes: [][]byte{[]byte("B0x1")}, RecipientNonce: nacl.NewNonce(), }, } @@ -68,7 +68,6 @@ func TestEncodePayloadWithRecipients(t *testing.T) { } } - func TestEncodePartyInfo(t *testing.T) { pi := PartyInfo{ @@ -112,4 +111,4 @@ func runEncodePartyInfoTest(t *testing.T, pi PartyInfo) { func toKey(encodedKey string) [nacl.KeySize]byte { key, _ := utils.LoadBase64Key(encodedKey) return *key -} \ No newline at end of file +} diff --git a/api/internal.go b/api/internal.go index 995dd6f..7d490d6 100644 --- a/api/internal.go +++ b/api/internal.go @@ -2,18 +2,25 @@ package api import ( "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/blk-io/chimera-api/chimera" + "github.com/blk-io/crux/utils" + "github.com/kevinburke/nacl" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" "io/ioutil" "math/rand" "net/http" - "time" - log "github.com/sirupsen/logrus" - "github.com/kevinburke/nacl" - "github.com/blk-io/crux/utils" - "encoding/hex" "net/http/httputil" - "fmt" + "net/url" + "time" ) +// EncryptedPayload is the struct used for storing all data associated with an encrypted +// transaction. type EncryptedPayload struct { Sender nacl.Key CipherText []byte @@ -22,20 +29,27 @@ type EncryptedPayload struct { RecipientNonce nacl.Nonce } +// PartyInfo is a struct that stores details of all enclave nodes (or parties) on the network. type PartyInfo struct { - url string - // public key -> URL - recipients map[[nacl.KeySize]byte]string - parties map[string]bool // URLs + url string // URL identifying this node + recipients map[[nacl.KeySize]byte]string // public key -> URL + parties map[string]bool // Node (or party) URLs client utils.HttpClient + grpc bool } +// GetRecipient retrieves the URL associated with the provided recipient. func (s *PartyInfo) GetRecipient(key nacl.Key) (string, bool) { value, ok := s.recipients[*key] return value, ok } -func InitPartyInfo(rawUrl string, otherNodes []string, client utils.HttpClient) PartyInfo { +func (s *PartyInfo) GetAllValues() (string, map[[nacl.KeySize]byte]string, map[string]bool) { + return s.url, s.recipients, s.parties +} + +// InitPartyInfo initializes a new PartyInfo store. +func InitPartyInfo(rawUrl string, otherNodes []string, client utils.HttpClient, grpc bool) PartyInfo { parties := make(map[string]bool) for _, node := range otherNodes { parties[node] = true @@ -46,9 +60,11 @@ func InitPartyInfo(rawUrl string, otherNodes []string, client utils.HttpClient) recipients: make(map[[nacl.KeySize]byte]string), parties: parties, client: client, + grpc: grpc, } } +// CreatePartyInfo creates a new PartyInfo struct. func CreatePartyInfo( url string, otherNodes []string, @@ -70,13 +86,64 @@ func CreatePartyInfo( } } +// RegisterPublicKeys associates the provided public keys with this node. func (s *PartyInfo) RegisterPublicKeys(pubKeys []nacl.Key) { for _, pubKey := range pubKeys { s.recipients[*pubKey] = s.url } } +func (s *PartyInfo) GetPartyInfoGrpc() { + recipients := make(map[string][]byte) + for key, url := range s.recipients { + recipients[url] = key[:] + } + urls := make(map[string]bool) + for k, v := range s.parties { + urls[k] = v + } + + for rawUrl := range urls { + if rawUrl == s.url { + continue + } + var completeUrl url.URL + url, err := completeUrl.Parse(rawUrl) + conn, err := grpc.Dial(url.Host, grpc.WithInsecure()) + if err != nil { + log.Errorf("Connection to gRPC server failed with error %s", err) + continue + } + defer conn.Close() + cli := chimera.NewClientClient(conn) + if cli == nil { + log.Errorf("Client is not intialised") + continue + } + party := chimera.PartyInfo{Url: rawUrl, Recipients: recipients, Parties: s.parties} + + partyInfoResp, err := cli.UpdatePartyInfo(context.Background(), &party) + if err != nil { + log.Errorf("Error in updating party info %s", err) + continue + } else { + log.Printf("Connected to the other node %s", rawUrl) + } + err = s.updatePartyInfoGrpc(*partyInfoResp, s.url) + if err != nil { + log.Errorf("Error: %s", err) + break + } + } +} + +// GetPartyInfo requests PartyInfo data from all remote nodes this node is aware of. The data +// provided in each response is applied to this node. func (s *PartyInfo) GetPartyInfo() { + if s.grpc { + s.GetPartyInfoGrpc() + return + } encodedPartyInfo := EncodePartyInfo(*s) // First copy our endpoints as we update this map in place @@ -98,7 +165,8 @@ func (s *PartyInfo) GetPartyInfo() { } var req *http.Request - req, err = http.NewRequest("POST", endPoint, bytes.NewBuffer(encodedPartyInfo[:])) + encoded := s.getEncoded(encodedPartyInfo) + req, err = http.NewRequest("POST", endPoint, bytes.NewBuffer(encoded)) if err != nil { log.WithField("url", rawUrl).Errorf( @@ -114,25 +182,61 @@ func (s *PartyInfo) GetPartyInfo() { "Error sending /partyinfo request, %v", err) continue } - + if resp.StatusCode != http.StatusOK { log.WithField("url", rawUrl).Errorf( "Error sending /partyinfo request, non-200 status code: %v", resp) continue } - var encoded []byte - encoded, err = ioutil.ReadAll(resp.Body) - resp.Body.Close() + err = s.updatePartyInfo(resp, rawUrl) + if err != nil { - log.WithField("url", rawUrl).Errorf( - "Unable to read partyInfo response from host, %v", err) break } - s.UpdatePartyInfo(encoded) } } +func (s *PartyInfo) updatePartyInfoGrpc(partyInfoReq chimera.PartyInfoResponse, rawUrl string) error { + pi, err := DecodePartyInfo(partyInfoReq.Payload) + if err != nil { + log.WithField("url", rawUrl).Errorf( + "Unable to decode partyInfo response from host, %v", err) + return err + } + s.UpdatePartyInfoGrpc(pi.url, pi.recipients, pi.parties) + return nil +} + +func (s *PartyInfo) updatePartyInfo(resp *http.Response, rawUrl string) error { + var encoded []byte + encoded, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.WithField("url", rawUrl).Errorf( + "Unable to read partyInfo response from host, %v", err) + return err + } + s.UpdatePartyInfo(encoded) + return nil +} + +func (s *PartyInfo) getEncoded(encodedPartyInfo []byte) []byte { + if s.grpc { + recipients := make(map[string][]byte) + for key, url := range s.recipients { + recipients[url] = key[:] + } + e, err := json.Marshal(UpdatePartyInfo{s.url, recipients, s.parties}) + if err != nil { + log.Errorf("Marshalling failed %v", err) + return nil + } + return e + } + return encodedPartyInfo[:] +} + func (s *PartyInfo) PollPartyInfo() { time.Sleep(time.Duration(rand.Intn(16)) * time.Second) s.GetPartyInfo() @@ -142,9 +246,9 @@ func (s *PartyInfo) PollPartyInfo() { go func() { for { select { - case <- ticker.C: + case <-ticker.C: s.GetPartyInfo() - case <- quit: + case <-quit: ticker.Stop() return } @@ -152,9 +256,10 @@ func (s *PartyInfo) PollPartyInfo() { }() } -// This can happen from the /partyinfo server endpoint being hit, or -// by a response from us hitting another nodes /partyinfo endpoint -// TODO: Control access via a channel for updates +// UpdatePartyInfo updates the PartyInfo datastore with the provided encoded data. +// This can happen from the /partyinfo server endpoint being hit, or by a response from us hitting +// another nodes /partyinfo endpoint. +// TODO: Control access via a channel for updates. func (s *PartyInfo) UpdatePartyInfo(encoded []byte) { log.Debugf("Updating party info payload: %s", hex.EncodeToString(encoded)) pi, err := DecodePartyInfo(encoded) @@ -180,6 +285,60 @@ func (s *PartyInfo) UpdatePartyInfo(encoded []byte) { } } +func (s *PartyInfo) UpdatePartyInfoGrpc(url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool) { + for publicKey, url := range recipients { + // we should ignore messages about ourselves + // in order to stop people masquerading as you, there + // should be a digital signature associated with each + // url -> node broadcast + if url != s.url { + s.recipients[publicKey] = url + } + } + + for url := range parties { + // we don't want to broadcast party info to ourselves + s.parties[url] = true + } +} + +func PushGrpc(encoded []byte, path string, epl EncryptedPayload) error { + var completeUrl url.URL + url, err := completeUrl.Parse(path) + conn, err := grpc.Dial(url.Host, grpc.WithInsecure()) + if err != nil { + log.Fatalf("Connection to gRPC server failed with error %s", err) + } + defer conn.Close() + cli := chimera.NewClientClient(conn) + if cli == nil { + log.Fatalf("Client is not intialised") + } + + var sender [32]byte + var nonce [32]byte + var recipientNonce [32]byte + + copy(sender[:], (*epl.Sender)[:]) + copy(nonce[:], (*epl.Nonce)[:]) + copy(recipientNonce[:], (*epl.RecipientNonce)[:]) + encrypt := chimera.EncryptedPayload{ + Sender: sender[:], + CipherText: epl.CipherText, + Nonce: nonce[:], + ReciepientNonce: recipientNonce[:], + ReciepientBoxes: epl.RecipientBoxes, + } + pushPayload := chimera.PushPayload{Ep: &encrypt, Encoded: encoded} + _, err = cli.Push(context.Background(), &pushPayload) + if err != nil { + log.Errorf("Push failed with %s", err) + return err + } + return nil +} + +// Push is responsible for propagating the encoded payload to the given remote node. func Push(encoded []byte, url string, client utils.HttpClient) (string, error) { endPoint, err := utils.BuildUrl(url, "/push") @@ -223,4 +382,4 @@ func logRequest(r *http.Request) { log.Debugf("%q", dump) } -} \ No newline at end of file +} diff --git a/api/internal_test.go b/api/internal_test.go index 20f1166..29eb55f 100644 --- a/api/internal_test.go +++ b/api/internal_test.go @@ -1,9 +1,28 @@ package api import ( + "github.com/kevinburke/nacl" + "net/http" "testing" ) -func TestPush(t *testing.T) { +func TestRegisterPublicKeys(t *testing.T) { + key := []nacl.Key{nacl.NewKey()} + + pi := CreatePartyInfo( + "http://localhost:9000", + []string{"http://localhost:9001"}, + key, + http.DefaultClient) + + expKey := []nacl.Key{nacl.NewKey()} + expUrl := "http://localhost:9000" + + pi.RegisterPublicKeys(expKey) + + url, ok := pi.GetRecipient(expKey[0]) + if !ok || url != expUrl { + t.Errorf("Url is %s whereas %s is expected", url, expUrl) + } } diff --git a/config/config.go b/config/config.go index 0d25f08..561fae6 100644 --- a/config/config.go +++ b/config/config.go @@ -1,74 +1,91 @@ +// Package config provides the configuration settings to be used by the application at runtime package config import ( "flag" + "fmt" "github.com/spf13/pflag" "github.com/spf13/viper" "os" - "fmt" ) const ( - Verbosity = "verbosity" - AlwaysSendTo = "alwayssendto" - Storage = "storage" - WorkDir = "workdir" - Url = "url" - OtherNodes = "othernodes" - PublicKeys = "publickeys" - PrivateKeys = "privatekeys" - Port = "port" - Socket = "socket" + Verbosity = "verbosity" + VerbosityShorthand = "v" + AlwaysSendTo = "alwayssendto" + Storage = "storage" + WorkDir = "workdir" + Url = "url" + OtherNodes = "othernodes" + PublicKeys = "publickeys" + PrivateKeys = "privatekeys" + Port = "port" + Socket = "socket" GenerateKeys = "generate-keys" - BerkeleyDb = "berkeleydb" + BerkeleyDb = "berkeleydb" + UseGRPC = "grpc" + GrpcJsonPort = "grpcport" + NetworkInterface = "networkinterface" - Tls = "tls" - TlsServerChain = "tlsserverchain" - TlsServerTrust = "tlsservertrust" + Tls = "tls" + TlsServerChain = "tlsserverchain" + TlsServerTrust = "tlsservertrust" TlsKnownServers = "tlsknownservers" - TlsClientCert = "tlsclientcert" - TlsServerCert = "tlsservercert" + TlsClientCert = "tlsclientcert" + TlsServerCert = "tlsservercert" TlsKnownClients = "tlsknownclients" - TlsClientChain = "tlsclientchain" - TlsClientKey = "tlsclientkey" - TlsClientTrust = "tlsclienttrust" - TlsServerKey = "tlsserverkey" + TlsClientChain = "tlsclientchain" + TlsClientKey = "tlsclientkey" + TlsClientTrust = "tlsclienttrust" + TlsServerKey = "tlsserverkey" ) +// InitFlags initializes all supported command line flags. func InitFlags() { flag.String(GenerateKeys, "", "Generate a new keypair") flag.String(Url, "", "The URL to advertise to other nodes (reachable by them)") flag.Int(Port, -1, "The local port to listen on") - flag.String(WorkDir, ".", "The folder to put stuff in (default: .)") - flag.String(Socket, "", "IPC socket to create for access to the Private API") + flag.String(WorkDir, ".", "The folder to put stuff in ") + flag.String(Socket, "crux.ipc", "IPC socket to create for access to the Private API") flag.String(OtherNodes, "", "\"Boot nodes\" to connect to to discover the network") flag.String(PublicKeys, "", "Public keys hosted by this node") flag.String(PrivateKeys, "", "Private keys hosted by this node") flag.String(Storage, "crux.db", "Database storage file name") - flag.Bool(BerkeleyDb, false, "Use Berkeley DB for storage") + flag.Bool(BerkeleyDb, false, + "Use Berkeley DB for working with an existing Constellation data store [experimental]") - flag.Int(Verbosity, 1, "Verbosity level of logs") + flag.Int(Verbosity, 1, "Verbosity level of logs (0=fatal, 1=warn, 2=info, 3=debug)") + flag.Int(VerbosityShorthand, 1, "Verbosity level of logs (shorthand)") flag.String(AlwaysSendTo, "", "List of public keys for nodes to send all transactions too") + flag.Bool(UseGRPC, true, "Use gRPC server") + flag.Bool(Tls, false, "Use TLS to secure HTTP communications") + flag.String(TlsServerCert, "", "The server certificate to be used") + flag.String(TlsServerKey, "", "The server private key") + flag.Int(GrpcJsonPort, -1, "The local port to listen on for JSON extensions of gRPC") + flag.String(NetworkInterface, "localhost", "The network interface to bind the server to") // storage not currently supported as we use LevelDB - // TLS is not currently supported pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + viper.BindPFlags(pflag.CommandLine) // Binding the flags to test the initial configuration } +// Usage prints usage instructions to the console. func Usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %-25s%s\n", "crux.config", "Optional config file") pflag.PrintDefaults() } +// ParseCommandLine parses all provided command line arguments. func ParseCommandLine() { pflag.Parse() viper.BindPFlags(pflag.CommandLine) } +// LoadConfig loads all configuration settings in the provided configPath location. func LoadConfig(configPath string) error { viper.SetConfigType("hcl") viper.SetConfigFile(configPath) @@ -94,5 +111,3 @@ func GetString(key string) string { func GetStringSlice(key string) []string { return viper.GetStringSlice(key) } - - diff --git a/config/config_test.go b/config/config_test.go index bf9d1e6..1c4bc7b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,12 +1,33 @@ package config import ( - "testing" "reflect" + "testing" ) const configFile = "config_testdata.conf" +func TestInitFlags(t *testing.T) { + InitFlags() + conf := AllSettings() + expected := map[string]interface{}{ + Port: -1, + Verbosity: 1, + BerkeleyDb: false, + GenerateKeys: "", + AlwaysSendTo: "", + Storage: "crux.db", + WorkDir: ".", + Url: "", + PublicKeys: "", + OtherNodes: "", + PrivateKeys: "", + Socket: "crux.ipc", + } + + verifyConfig(t, conf, expected) +} + func TestLoadConfig(t *testing.T) { err := LoadConfig(configFile) @@ -17,27 +38,27 @@ func TestLoadConfig(t *testing.T) { conf := AllSettings() expected := map[string]interface{}{ - Verbosity: 1, - AlwaysSendTo: []interface{}{}, - TlsServerChain: []interface{}{}, - Storage: "dir:storage", - WorkDir: "data", - Url: "http://127.0.0.1:9001/", - TlsServerTrust: "tofu", - PublicKeys: [1]string{"foo.pub"}, - OtherNodes: [1]string{"http://127.0.0.1:9000/"}, - TlsKnownServers:"tls-known-servers", - TlsClientCert: "tls-client-cert.pem", - PrivateKeys: [1]string{"foo.key"}, - TlsServerCert: "tls-server-cert.pem", - Tls: "strict", + Verbosity: 1, + AlwaysSendTo: []interface{}{}, + TlsServerChain: []interface{}{}, + Storage: "dir:storage", + WorkDir: "data", + Url: "http://127.0.0.1:9001/", + TlsServerTrust: "tofu", + PublicKeys: []interface{}{"foo.pub"}, + OtherNodes: []interface{}{"http://127.0.0.1:9000/"}, + TlsKnownServers: "tls-known-servers", + TlsClientCert: "tls-client-cert.pem", + PrivateKeys: []interface{}{"foo.key"}, + TlsServerCert: "tls-server-cert.pem", + Tls: "strict", TlsKnownClients: "tls-known-clients", - TlsClientChain: []interface{}{}, - TlsClientKey: "tls-client-key.pem", - Socket: "constellation.ipc", - TlsClientTrust: "ca-or-tofu", - TlsServerKey: "tls-server-key.pem", - Port: 9001, + TlsClientChain: []interface{}{}, + TlsClientKey: "tls-client-key.pem", + Socket: "constellation.ipc", + TlsClientTrust: "ca-or-tofu", + TlsServerKey: "tls-server-key.pem", + Port: 9001, } verifyConfig(t, conf, expected) @@ -46,18 +67,29 @@ func TestLoadConfig(t *testing.T) { func verifyConfig(t *testing.T, conf map[string]interface{}, expected map[string]interface{}) { for expK, expV := range expected { //if conf[key] != value { - if actV, ok := conf[expK]; !ok { + if actV, ok := conf[expK]; ok { var eq bool - switch actV.(type) { // we cannot use == for equality with []interface{} + switch actV.(type) { // we cannot use == for equality with []interface{} case []interface{}: eq = reflect.DeepEqual(actV, expV) default: eq = actV == expV } - if !eq { t.Errorf("Key: %s with value %v could not be found", expK, expV) } } } } + +func TestGetBoolConfig(t *testing.T) { + if GetBool(Verbosity) != true { + t.Errorf("Verbosity is %t", GetBool(Verbosity)) + } +} + +func TestGetIntConfig(t *testing.T) { + if GetInt(Port) != 9001 { + t.Errorf("Port num 9001 is expected but we got %d", GetInt(Port)) + } +} diff --git a/crux.go b/crux.go index b39a496..426491d 100644 --- a/crux.go +++ b/crux.go @@ -1,16 +1,16 @@ package main import ( - "os" - "path" - "net/http" - "strings" "github.com/blk-io/crux/api" "github.com/blk-io/crux/config" "github.com/blk-io/crux/enclave" "github.com/blk-io/crux/server" "github.com/blk-io/crux/storage" log "github.com/sirupsen/logrus" + "net/http" + "os" + "path" + "strings" "time" ) @@ -25,7 +25,7 @@ func main() { for _, arg := range args[1:] { if strings.Contains(arg, ".conf") { - err := config.LoadConfig(os.Args[0]) + err := config.LoadConfig(arg) if err != nil { log.Fatalln(err) } @@ -34,7 +34,13 @@ func main() { } config.ParseCommandLine() - verbosity := config.GetInt(config.Verbosity) + verbosity := 1 + if config.GetInt(config.Verbosity) > config.GetInt(config.VerbosityShorthand) { + verbosity = config.GetInt(config.Verbosity) + } else { + verbosity = config.GetInt(config.VerbosityShorthand) + } + var level log.Level switch verbosity { @@ -44,7 +50,7 @@ func main() { level = log.WarnLevel case 2: level = log.InfoLevel - case 3: + default: level = log.DebugLevel } log.SetLevel(level) @@ -64,7 +70,6 @@ func main() { ipcFile := config.GetString(config.Socket) storagePath := path.Join(workDir, dbStorage) ipcPath := path.Join(workDir, ipcFile) - var db storage.DataStore var err error if config.GetBool(config.BerkeleyDb) { @@ -78,19 +83,27 @@ func main() { } defer db.Close() - otherNodes := config.GetStringSlice(config.OtherNodes) + allOtherNodes := config.GetString(config.OtherNodes) + otherNodes := strings.Split(allOtherNodes, ",") url := config.GetString(config.Url) if url == "" { log.Fatalln("URL must be specified") } - + port := config.GetInt(config.Port) + if port < 0 { + log.Fatalln("Port must be specified") + } httpClient := &http.Client{ Timeout: time.Second * 10, } - pi := api.InitPartyInfo(url, otherNodes, httpClient) + grpc := config.GetBool(config.UseGRPC) - privKeyFiles := config.GetStringSlice(config.PrivateKeys) - pubKeyFiles := config.GetStringSlice(config.PublicKeys) + pi := api.InitPartyInfo(url, otherNodes, httpClient, grpc) + + privKeys := config.GetString(config.PrivateKeys) + pubKeys := config.GetString(config.PublicKeys) + pubKeyFiles := strings.Split(pubKeys, ",") + privKeyFiles := strings.Split(privKeys, ",") if len(privKeyFiles) != len(pubKeyFiles) { log.Fatalln("Private keys provided must have corresponding public keys") @@ -108,16 +121,26 @@ func main() { pubKeyFiles[i] = path.Join(workDir, keyFile) } - enc := enclave.Init(db, pubKeyFiles, privKeyFiles, pi, http.DefaultClient) + enc := enclave.Init(db, pubKeyFiles, privKeyFiles, pi, http.DefaultClient, grpc) pi.RegisterPublicKeys(enc.PubKeys) - port := config.GetInt(config.Port) - if port < 0 { - log.Fatalln("Port must be specified") - } + tls := config.GetBool(config.Tls) + var tlsCertFile, tlsKeyFile string + if tls { + servCert := config.GetString(config.TlsServerCert) + servKey := config.GetString(config.TlsServerKey) + + if (len(servCert) != len(servKey)) || (len(servCert) <= 0) { + log.Fatalf("Please provide server certificate and key for TLS %s %s %d ", servKey, servCert, len(servCert)) + } - _, err = server.Init(enc, port, ipcPath) + tlsCertFile = path.Join(workDir, servCert) + tlsKeyFile = path.Join(workDir, servKey) + } + grpcJsonport := config.GetInt(config.GrpcJsonPort) + networkInterface := config.GetString(config.NetworkInterface) + _, err = server.Init(enc, networkInterface, port, ipcPath, grpc, grpcJsonport, tls, tlsCertFile, tlsKeyFile) if err != nil { log.Fatalf("Error starting server: %v\n", err) } @@ -130,4 +153,4 @@ func main() { func exit() { config.Usage() os.Exit(1) -} \ No newline at end of file +} diff --git a/docker/crux/Dockerfile b/docker/crux/Dockerfile new file mode 100644 index 0000000..7d22fd8 --- /dev/null +++ b/docker/crux/Dockerfile @@ -0,0 +1,30 @@ +FROM alpine:3.8 + +RUN apk update --no-cache && \ + # Update and then install dependencies + apk add unzip db zlib wrk wget libsodium-dev go bash libpthread-stubs db-dev && \ + apk -X http://dl-cdn.alpinelinux.org/alpine/edge/testing add leveldb && \ + apk add build-base cmake boost-dev git + +ENV CRUX_PUB="" +ENV CRUX_PRIV="" +ENV OWN_URL="" +ENV OTHER_NODES="" +ENV PORT="" + +RUN git clone https://github.com/blk-io/crux.git + +WORKDIR /crux + +RUN make setup && \ + make build && \ + apk del sed make git cmake build-base gcc g++ musl-dev curl-dev boost-dev +# fails https://github.com/golang/go/issues/14481 +# RUN make test + +EXPOSE 9000 + +COPY start.sh start.sh +RUN chmod +x start.sh + +ENTRYPOINT ["./start.sh"] \ No newline at end of file diff --git a/docker/crux/docker-compose.yml b/docker/crux/docker-compose.yml new file mode 100644 index 0000000..b103196 --- /dev/null +++ b/docker/crux/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.4" +services: + node1: + image: blk.io/quorum/crux + build: + context: . + container_name: crux1 + ports: + - 9001:9000 + environment: + - CRUX_PUB=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= + - CRUX_PRIV={"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"} + - OTHER_NODES=http://node2:9000/ + - OWN_URL=node1 + - PORT=9000 + + node2: + image: blk.io/quorum/crux + build: + context: . + container_name: crux2 + ports: + - 9002:9000 + environment: + - CRUX_PUB=QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc= + - CRUX_PRIV={"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"} + - OTHER_NODES=http://node1:9000/ + - OWN_URL=node2 + - PORT=9000 diff --git a/docker/crux/start.sh b/docker/crux/start.sh new file mode 100644 index 0000000..c31a713 --- /dev/null +++ b/docker/crux/start.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +echo $CRUX_PUB >> key.pub +echo $CRUX_PRIV >> key.priv +CMD="./bin/crux --url=http://$OWN_URL:$PORT/ --port=$PORT --networkinterface 0.0.0.0 --publickeys=key.pub --privatekeys=key.priv --othernodes=$OTHER_NODES --verbosity=3" +$CMD >> "crux.log" 2>&1 \ No newline at end of file diff --git a/docker/quorum-crux/Dockerfile b/docker/quorum-crux/Dockerfile new file mode 100644 index 0000000..fefc57a --- /dev/null +++ b/docker/quorum-crux/Dockerfile @@ -0,0 +1,40 @@ +FROM alpine:3.8 + +RUN apk update --no-cache && \ + # Update and then install dependencies + apk add unzip db zlib wrk wget libsodium-dev go bash libpthread-stubs db-dev && \ + apk -X http://dl-cdn.alpinelinux.org/alpine/edge/testing add leveldb && \ + apk add build-base cmake boost-dev git + +ENV PORT="" +ENV NODE_KEY="" +ENV CRUX_PUB="" +ENV GETH_KEY="" +ENV OWN_URL="" +ENV CRUX_PRIV="" +ENV OTHER_NODES="" +ENV GETH_RPC_PORT="" +ENV GETH_PORT="" + +WORKDIR /quorum + +COPY bootstrap.sh bootstrap.sh +COPY istanbul-genesis.json istanbul-genesis.json +COPY passwords.txt passwords.txt +COPY istanbul-init.sh istanbul-init.sh +COPY crux-start.sh crux-start.sh +COPY istanbul-start.sh istanbul-start.sh +COPY start.sh start.sh +COPY scripts/simpleContract.js simpleContract.js +COPY scripts/test_transaction.sh test_transaction.sh + +RUN chmod +x start.sh crux-start.sh istanbul-start.sh istanbul-init.sh && \ + chmod +x test_transaction.sh && \ + chmod +x bootstrap.sh && \ + ./bootstrap.sh && \ + apk del sed make git cmake build-base gcc g++ musl-dev curl-dev boost-dev + +EXPOSE 9000 21000 22000 + +# Entrypoint for container +ENTRYPOINT ["./start.sh"] diff --git a/docker/quorum-crux/bootstrap.sh b/docker/quorum-crux/bootstrap.sh new file mode 100644 index 0000000..a7b519f --- /dev/null +++ b/docker/quorum-crux/bootstrap.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eu -o pipefail + +# make/install quorum +git clone https://github.com/ConsenSys/quorum.git +pushd quorum >/dev/null +git checkout tags/v2.0.3-grpc +make all +cp build/bin/geth /usr/local/bin +cp build/bin/bootnode /usr/local/bin +rm -r build +popd >/dev/null + +# make/install crux +git clone https://github.com/blk-io/crux.git +cd crux +git checkout tags/v1.0.3 +make setup && make +cp bin/crux /usr/local/bin +rm -r bin diff --git a/docker/quorum-crux/crux-start.sh b/docker/quorum-crux/crux-start.sh new file mode 100644 index 0000000..2d0fca0 --- /dev/null +++ b/docker/quorum-crux/crux-start.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -u +set -e + +DDIR="qdata/c" +mkdir -p $DDIR +mkdir -p qdata/logs +echo $CRUX_PUB >> "$DDIR/tm.pub" +echo $CRUX_PRIV >> "$DDIR/tm.key" +rm -f "$DDIR/tm.ipc" +CMD="crux --url=http://$OWN_URL:$PORT/ --port=$PORT --networkinterface 0.0.0.0 --workdir=$DDIR --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=$OTHER_NODES --verbosity=3" +$CMD >> "qdata/logs/crux.log" 2>&1 & + +DOWN=true +while $DOWN; do + sleep 0.1 + DOWN=false + if [ ! -S "qdata/c/tm.ipc" ]; then + DOWN=true + fi +done diff --git a/docker/quorum-crux/docker-compose-local.yaml b/docker/quorum-crux/docker-compose-local.yaml new file mode 100644 index 0000000..94ed4e8 --- /dev/null +++ b/docker/quorum-crux/docker-compose-local.yaml @@ -0,0 +1,105 @@ +# build the docker container locally +version: "3.4" +services: + + node1: &quorum_crux_node + # Pull image down from Docker Hub + # image: blkio10/quorum-crux:v1.0.0 + # Uncomment the below, and comment out the above line to build the Docker images yourself + image: blk.io/quorum/quorum-crux + build: + context: . + container_name: quorum1 + ports: + - 22001:22000 + - 21001:21000 + - 9001:9000 + restart: always + networks: + quorum_net: + ipv4_address: 10.5.0.11 + environment: + - GETH_KEY={"address":"ed9d02e382b34818e88b88a309c7fe71e65f419d","crypto":{"cipher":"aes-128-ctr","ciphertext":"4e77046ba3f699e744acb4a89c36a3ea1158a1bd90a076d36675f4c883864377","cipherparams":{"iv":"a8932af2a3c0225ee8e872bc0e462c11"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8ca49552b3e92f79c51f2cd3d38dfc723412c212e702bd337a3724e8937aff0f"},"mac":"6d1354fef5aa0418389b1a5d1f5ee0050d7273292a1171c51fd02f9ecff55264"},"id":"a65d1ac3-db7e-445d-a1cc-b6c5eeaa05e0","version":3} + - NODE_KEY=633b2ef3ed5306f05a532eb44eb84858aca470d4fde039c1c988088e48070e64 + - CRUX_PUB=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= + - CRUX_PRIV={"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node1 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node2:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + + node2: + <<: *quorum_crux_node + container_name: quorum2 + ports: + - 22002:22000 + - 21002:21000 + - 9002:9000 + networks: + quorum_net: + ipv4_address: 10.5.0.12 + environment: + - GETH_KEY={"address":"ca843569e3427144cead5e4d5999a3d0ccf92b8e","crypto":{"cipher":"aes-128-ctr","ciphertext":"01d409941ce57b83a18597058033657182ffb10ae15d7d0906b8a8c04c8d1e3a","cipherparams":{"iv":"0bfb6eadbe0ab7ffaac7e1be285fb4e5"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"7b90f455a95942c7c682e0ef080afc2b494ef71e749ba5b384700ecbe6f4a1bf"},"mac":"4cc851f9349972f851d03d75a96383a37557f7c0055763c673e922de55e9e307"},"id":"354e3b35-1fed-407d-a358-889a29111211","version":3} + - NODE_KEY=d5b8dfced693dbb7bf858dce40e3b2a0373696ac88ccf5e1c0052e26b7d77e49 + - CRUX_PUB=QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc= + - CRUX_PRIV={"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node2 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node3:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + + node3: + <<: *quorum_crux_node + container_name: quorum3 + ports: + - 22003:22000 + - 9003:9000 + - 21003:21000 + networks: + quorum_net: + ipv4_address: 10.5.0.13 + environment: + - GETH_KEY={"address":"0fbdc686b912d7722dc86510934589e0aaf3b55a","crypto":{"cipher":"aes-128-ctr","ciphertext":"6b2c72c6793f3da8185e36536e02f574805e41c18f551f24b58346ef4ecf3640","cipherparams":{"iv":"582f27a739f39580410faa108d5cc59f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"1a79b0db3f8cb5c2ae4fa6ccb2b5917ce446bd5e42c8d61faeee512b97b4ad4a"},"mac":"cecb44d2797d6946805d5d744ff803805477195fab1d2209eddc3d1158f2e403"},"id":"f7292e90-af71-49af-a5b3-40e8493f4681","version":3} + - NODE_KEY=45d87820ad7792f86592a8e3494c3e78b4755a480cfad1799d6c3f28c3f0d87c + - CRUX_PUB=1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg= + - CRUX_PRIV={"data":{"bytes":"tMxUVR8bX7aq/TbpVHc2QV3SN2iUuExBwefAuFsO0Lg="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node3 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node4:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + + node4: + <<: *quorum_crux_node + container_name: quorum4 + ports: + - 22004:22000 + - 9004:9000 + - 21004:21000 + networks: + quorum_net: + ipv4_address: 10.5.0.14 + environment: + - GETH_KEY={"address":"9186eb3d20cbd1f5f992a950d808c4495153abd5","crypto":{"cipher":"aes-128-ctr","ciphertext":"d160a630a39be3ff35556055406d8ff2a635f0535fe298d62ccc812d8f7b3bd5","cipherparams":{"iv":"82fce06bc6e1658a5e81ccef3b753329"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8d0c486db4c942721f4f5e96d48e9344805d101dad8159962b8a2008ac718548"},"mac":"4a92bda949068968d470320260ae1a825aa22f6a40fb8567c9f91d700c3f7e91"},"id":"bdb3b4f6-d8d0-4b00-8473-e223ef371b5c","version":3} + - NODE_KEY=daa89d4ae250d24b33847343d0cc0116c48331b81e28514522bb7f77f2be5676 + - CRUX_PUB=oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8= + - CRUX_PRIV={"data":{"bytes":"grQjd3dBp4qFs8/5Jdq7xjz++aUx/LXAqISFyPWaCRw="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node4 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node1:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + +networks: + quorum_net: + driver: bridge + ipam: + driver: default + config: + - subnet: 10.5.0.0/24 diff --git a/docker/quorum-crux/docker-compose.yaml b/docker/quorum-crux/docker-compose.yaml new file mode 100644 index 0000000..1ecd1f1 --- /dev/null +++ b/docker/quorum-crux/docker-compose.yaml @@ -0,0 +1,104 @@ +version: "3.4" +services: + + node1: &quorum_crux_node + # Pull image down from Docker Hub + image: blkio10/quorum-crux:v1.0.0 + # Uncomment the below, and comment out the above line to build the Docker images yourself +# image: blk.io/quorum/quorum-crux +# build: +# context: . + container_name: quorum1 + ports: + - 22001:22000 + - 21001:21000 + - 9001:9000 + restart: always + networks: + quorum_net: + ipv4_address: 10.5.0.11 + environment: + - GETH_KEY={"address":"ed9d02e382b34818e88b88a309c7fe71e65f419d","crypto":{"cipher":"aes-128-ctr","ciphertext":"4e77046ba3f699e744acb4a89c36a3ea1158a1bd90a076d36675f4c883864377","cipherparams":{"iv":"a8932af2a3c0225ee8e872bc0e462c11"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8ca49552b3e92f79c51f2cd3d38dfc723412c212e702bd337a3724e8937aff0f"},"mac":"6d1354fef5aa0418389b1a5d1f5ee0050d7273292a1171c51fd02f9ecff55264"},"id":"a65d1ac3-db7e-445d-a1cc-b6c5eeaa05e0","version":3} + - NODE_KEY=633b2ef3ed5306f05a532eb44eb84858aca470d4fde039c1c988088e48070e64 + - CRUX_PUB=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= + - CRUX_PRIV={"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node1 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node2:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + + node2: + <<: *quorum_crux_node + container_name: quorum2 + ports: + - 22002:22000 + - 21002:21000 + - 9002:9000 + networks: + quorum_net: + ipv4_address: 10.5.0.12 + environment: + - GETH_KEY={"address":"ca843569e3427144cead5e4d5999a3d0ccf92b8e","crypto":{"cipher":"aes-128-ctr","ciphertext":"01d409941ce57b83a18597058033657182ffb10ae15d7d0906b8a8c04c8d1e3a","cipherparams":{"iv":"0bfb6eadbe0ab7ffaac7e1be285fb4e5"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"7b90f455a95942c7c682e0ef080afc2b494ef71e749ba5b384700ecbe6f4a1bf"},"mac":"4cc851f9349972f851d03d75a96383a37557f7c0055763c673e922de55e9e307"},"id":"354e3b35-1fed-407d-a358-889a29111211","version":3} + - NODE_KEY=d5b8dfced693dbb7bf858dce40e3b2a0373696ac88ccf5e1c0052e26b7d77e49 + - CRUX_PUB=QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc= + - CRUX_PRIV={"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node2 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node3:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + + node3: + <<: *quorum_crux_node + container_name: quorum3 + ports: + - 22003:22000 + - 9003:9000 + - 21003:21000 + networks: + quorum_net: + ipv4_address: 10.5.0.13 + environment: + - GETH_KEY={"address":"0fbdc686b912d7722dc86510934589e0aaf3b55a","crypto":{"cipher":"aes-128-ctr","ciphertext":"6b2c72c6793f3da8185e36536e02f574805e41c18f551f24b58346ef4ecf3640","cipherparams":{"iv":"582f27a739f39580410faa108d5cc59f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"1a79b0db3f8cb5c2ae4fa6ccb2b5917ce446bd5e42c8d61faeee512b97b4ad4a"},"mac":"cecb44d2797d6946805d5d744ff803805477195fab1d2209eddc3d1158f2e403"},"id":"f7292e90-af71-49af-a5b3-40e8493f4681","version":3} + - NODE_KEY=45d87820ad7792f86592a8e3494c3e78b4755a480cfad1799d6c3f28c3f0d87c + - CRUX_PUB=1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg= + - CRUX_PRIV={"data":{"bytes":"tMxUVR8bX7aq/TbpVHc2QV3SN2iUuExBwefAuFsO0Lg="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node3 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node4:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + + node4: + <<: *quorum_crux_node + container_name: quorum4 + ports: + - 22004:22000 + - 9004:9000 + - 21004:21000 + networks: + quorum_net: + ipv4_address: 10.5.0.14 + environment: + - GETH_KEY={"address":"9186eb3d20cbd1f5f992a950d808c4495153abd5","crypto":{"cipher":"aes-128-ctr","ciphertext":"d160a630a39be3ff35556055406d8ff2a635f0535fe298d62ccc812d8f7b3bd5","cipherparams":{"iv":"82fce06bc6e1658a5e81ccef3b753329"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8d0c486db4c942721f4f5e96d48e9344805d101dad8159962b8a2008ac718548"},"mac":"4a92bda949068968d470320260ae1a825aa22f6a40fb8567c9f91d700c3f7e91"},"id":"bdb3b4f6-d8d0-4b00-8473-e223ef371b5c","version":3} + - NODE_KEY=daa89d4ae250d24b33847343d0cc0116c48331b81e28514522bb7f77f2be5676 + - CRUX_PUB=oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8= + - CRUX_PRIV={"data":{"bytes":"grQjd3dBp4qFs8/5Jdq7xjz++aUx/LXAqISFyPWaCRw="},"type":"unlocked"} + - PORT=9000 + - OWN_URL=node4 + - GETH_PORT=21000 + - GETH_RPC_PORT=22000 + - OTHER_NODES=http://node1:9000/ + - PERMISSIONED_NODES=["enode://5c3c98e3a28a87e73ab40468212de7ab6cf0e2afa77781295925f32369c00baf30f664e52f8d152c02b069d6daa1a61f477e3c1eca64403529dfbd0c31e09524@10.5.0.11:21000?discport=0","enode://9b98a96a8ba080ff4c7863e5fdf3211a7082b612d5897ae4eed687eec391eb421c8ed7c572ca17f257441a0cb544a7c184244dfdf9a114f5251da3dac72e7585@10.5.0.12:21000?discport=0","enode://a51690b44ab39fd83c42b5a7c087ba222970951f06655ebbba1625267fad105fd238c9f092e05b2293f526e748b2fa423b22d66296f770037393c26a9e5d3543@10.5.0.13:21000?discport=0","enode://a68df7cd75e9ea490653bdba7c6868f979944578e59c9efd2aa62878822f16f46a49a13289f6392923053be1acb3a6ec8e2fc92cae59de859fd5892071fbfa88@10.5.0.14:21000?discport=0"] + +networks: + quorum_net: + driver: bridge + ipam: + driver: default + config: + - subnet: 10.5.0.0/24 \ No newline at end of file diff --git a/docker/quorum-crux/istanbul-genesis.json b/docker/quorum-crux/istanbul-genesis.json new file mode 100644 index 0000000..4d782c2 --- /dev/null +++ b/docker/quorum-crux/istanbul-genesis.json @@ -0,0 +1,50 @@ +{ + "config": { + "chainId": 2017, + "homesteadBlock": 1, + "eip150Block": 2, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 3, + "eip158Block": 3, + "istanbul": { + "epoch": 30000, + "policy": 0 + } + }, + "nonce": "0x0", + "timestamp": "0x5b68584d", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f89af85494c816ef5f432f9aff47b74120a9d6b41ea9a3bb1b946e98452e06d5158133490981a66f626538ec2c44947785902068b03c3820f8168af31845ffb313bdcd94c5263ce01b7c1fc0bc81d348a84df9f7bca54492b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gasLimit": "0x1312D00", + "difficulty": "0x1", + "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "6e98452e06d5158133490981a66f626538ec2c44": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "7785902068b03c3820f8168af31845ffb313bdcd": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "c5263ce01b7c1fc0bc81d348a84df9f7bca54492": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "c816ef5f432f9aff47b74120a9d6b41ea9a3bb1b": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "ed9d02e382b34818e88b88a309c7fe71e65f419d": { + "balance": "1000000000000000000000000000" + }, + "ca843569e3427144cead5e4d5999a3d0ccf92b8e": { + "balance": "1000000000000000000000000000" + }, + "0fbdc686b912d7722dc86510934589e0aaf3b55a": { + "balance": "1000000000000000000000000000" + }, + "9186eb3d20cbd1f5f992a950d808c4495153abd5": { + "balance": "1000000000000000000000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/docker/quorum-crux/istanbul-init.sh b/docker/quorum-crux/istanbul-init.sh new file mode 100644 index 0000000..700fddd --- /dev/null +++ b/docker/quorum-crux/istanbul-init.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -u +set -e + +echo "[*] Cleaning up temporary data directories" +rm -rf qdata +mkdir -p qdata/logs + +echo "[*] Configuring node" +mkdir -p qdata/dd/{keystore,geth} +echo $PERMISSIONED_NODES >> qdata/dd/static-nodes.json +echo $PERMISSIONED_NODES >> qdata/dd/permissioned-nodes.json +echo $GETH_KEY >> qdata/dd/keystore/key +geth --datadir qdata/dd init istanbul-genesis.json diff --git a/docker/quorum-crux/istanbul-start.sh b/docker/quorum-crux/istanbul-start.sh new file mode 100644 index 0000000..11d2f2f --- /dev/null +++ b/docker/quorum-crux/istanbul-start.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -u +set -e + +mkdir -p qdata/logs +echo "[*] Starting Crux nodes" +./crux-start.sh + +echo "[*] Starting Ethereum nodes" +set -v +ARGS="--syncmode full --mine --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul " +ARGS=$ARGS"--txpool.globalslots 20000 --txpool.globalqueue 20000 --istanbul.blockperiod 1 " +# ARGS=$ARGS"--cache 4096 --trie-cache-gens 1000 " +PRIVATE_CONFIG=qdata/c/tm.ipc nohup geth --datadir qdata/dd $ARGS --rpcport $GETH_RPC_PORT --port $GETH_PORT --nodekeyhex $NODE_KEY --unlock 0 --password passwords.txt --verbosity=6 2>>qdata/logs/node.log & +set +v + + diff --git a/docker/quorum-crux/passwords.txt b/docker/quorum-crux/passwords.txt new file mode 100644 index 0000000..e69de29 diff --git a/docker/quorum-crux/scripts/simpleContract.js b/docker/quorum-crux/scripts/simpleContract.js new file mode 100644 index 0000000..238f5d4 --- /dev/null +++ b/docker/quorum-crux/scripts/simpleContract.js @@ -0,0 +1,22 @@ +a = eth.accounts[0] +web3.eth.defaultAccount = a; + +// abi and bytecode generated from simplestorage.sol: +// > solcjs --bin --abi simplestorage.sol +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"}],"payable":false,"type":"constructor"}]; + +var bytecode = "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029"; + +var simpleContract = web3.eth.contract(abi); +var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="]}, function(e, contract) { + if (e) { + console.log("err creating contract", e); + } else { + if (!contract.address) { + console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined..."); + } else { + console.log("Contract mined! Address: " + contract.address); + console.log(contract); + } + } +}); diff --git a/docker/quorum-crux/scripts/test_transaction.sh b/docker/quorum-crux/scripts/test_transaction.sh new file mode 100644 index 0000000..a22f0b4 --- /dev/null +++ b/docker/quorum-crux/scripts/test_transaction.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Sending private transaction" +PRIVATE_CONFIG=qdata/c/tm.ipc geth --exec "loadScript(\"simpleContract.js\")" attach ipc:qdata/dd/geth.ipc diff --git a/docker/quorum-crux/start.sh b/docker/quorum-crux/start.sh new file mode 100644 index 0000000..4d9eda1 --- /dev/null +++ b/docker/quorum-crux/start.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +./istanbul-init.sh +./istanbul-start.sh + +tail -f /dev/null \ No newline at end of file diff --git a/docs/new-tx.mermaid b/docs/new-tx.mermaid new file mode 100644 index 0000000..761de33 --- /dev/null +++ b/docs/new-tx.mermaid @@ -0,0 +1,10 @@ +sequenceDiagram + participant Client + participant Quorum + participant Crux + participant Remote Crux + +Client->>Quorum: send(transaction, privateFor) +Quorum->>Crux: send(transaction, privateFor) +Crux->>Remote Crux: push(encryptedPayload) +Crux-->>Quorum: sendResponse(transactionHash) diff --git a/docs/new-tx.svg b/docs/new-tx.svg new file mode 100644 index 0000000..8719b28 --- /dev/null +++ b/docs/new-tx.svg @@ -0,0 +1,350 @@ +ClientQuorumCruxRemote Cruxsend(transaction, privateFor)send(transaction, privateFor)push(encryptedPayload)sendResponse(transactionHash)ClientQuorumCruxRemote Crux \ No newline at end of file diff --git a/docs/quorum-architecture.png b/docs/quorum-architecture.png new file mode 100644 index 0000000..c6c2fe4 Binary files /dev/null and b/docs/quorum-architecture.png differ diff --git a/docs/quorum-architecture.xml b/docs/quorum-architecture.xml new file mode 100644 index 0000000..1b5c87e --- /dev/null +++ b/docs/quorum-architecture.xml @@ -0,0 +1 @@ +7Ztbb+MoFMc/TaTuPPne9DFJm7bSdJSdVFvtI7FJgorBwuS2n37BxqlvaUjjOu2uOw+DD2Ab/j/D4UB69ijc3jMQLZ9oAHHPMoJtz77tWZbZ927Ef9KyUxbDvE4tC4YCZXszTNE/MCuorCsUwLhQkFOKOYqKRp8SAn1esAHG6KZYbE5x8akRWMCKYeoDXLW+oIAvldVzrbeMB4gWy+zRZtbkGfBfF4yuiHpgz7LnyV+aHYLsZqql8RIEdJMz2Xc9e8Qo5Wkq3I4glr2b9Vtab3wgd//iDBKuU8F20xprgFcwe+Xkxfgu642kOVBWMHr2cLNEHE4j4MvcjQBA2JY8xOLKFMl9g2TZBQZxrNIBiJfJTWQpn4bIVxkYzCAe7vttRDFlIotQAuX9OKOvMDOK7vSSv31Opo8tLHOEca7k3JX/pJ0SPgYhwpLHB4jXkCMfqAxFn9UX1wCjBREXGM5F7w2rnan6dw0Zh9ucSXXuPaQh5GwniqhcV4GffQmO0n2Tw8p1lXGZQ8rzlBEolhf7e7/JKRJK0QM4OBV176F47lkKn6iXkfxp6ZV+KJp6uVW9pCyiIB4oM6dRVkm1teb7OFnSfr8oqedWJL22ahR1rQYUtWs+Vw/Lxs9EYiETgyjCohc4okSUHCICkrd/JByyuRTVMq4Gw8c/sorikfu6rZLh+X04m+uQEQDYn/uaZJhejgwfymY38y3bRlF5y6gqb9Yp329A+OqXXBH+BWAMeSfribI63gVlrZt+S7I+odivF9UDoZSKzOIovU7KBGidFbkSDlJMhejWSDofiCCySNMojCgTrIzhVibyg0GufsfNO9x41gW58Y5zM8TUf/WXAJED9FRoSWrEKSCcARIDX84iqaUMmxGLLhU8fQV0Am/muXo+4Xxu+eehk74bZQFk5fc65Pvm3d0GyDOdInm21SJ518fJm3LAoS50A98XEnOFnRiwBHliSSeTATxAHqdMrMD+d+R9gg/TKjr94+jc/fWkC84jEX2+8pWfC7fQX8l0R8X5LlCrVNwcp2IkJiFI4lWsy8aEvnQcnO/StMqBaR4HYbojh3zhCgPjlby9nFTmIOZpKiYgipdUXWHZgK/ASd+a2XoxrcCF/cC5NCd2KQbiOG1yYh3n5BfkG8pedVHJbHEESGYM4DqyohSU3z8n2zwn+XIdPmc7IW5NCO3z8NEIod0CDmYg1vZhf8I1xLfDbig53/Vol4Vq+GXEVttWBbsbeMMawUyj1kn4DgFyIddNQdO6ELmd7Zg1HiI3qyvUP1eUrcJWZf3vbXsYx/c9bO+z9j1MjbXjw/PzRBQZTB67GPipn6xt2wV1Ww1mmhpLwFOn5B/dnHxGfDE7iHGJSTm777thIuJjsNaGwWcwiUhKXx4Sn+0iufURwCTVEdLAeNHqCtCqRgp+1C3hhpC9Yri7PbhVVlrKycM7QmDxcjimUvxVJDfHpKnEgugmXhS8KKnSOa+dMh2Zu0MUBPIhtXwVCWz6pI0WUDcN8WOVvAnvusqP49R5E03woxFBeH7b/xIlnwABC/HxaI43U0iCdLRh0IdorYaeiNEILJKRyODbbtxpYNxpNUL5zumbgoAllLLVx2F2ygSUbvB7Mur82g/xcnPJPXqdwx0n72z8Hoyf09FkMhw/d2PIB8YQ45J7oDonNyYMrdNZ4qSd9O+2d/7dwOlf8tiF1ub5VrSVAJwAEEai25NzFd2ccZLMVmnOuNaV2WlCZq3dcAa/lsTf7Ut2nQtKbGvEN/4WVcTaTjYzksuOryT2dzvUUN6sbldsjaVm595/PFB5Qffe1loNXk3oVPnrdNB5XeefSXJ0Nbab0FjDW2/grIlcBXLx5OxHO2Is6I6bfFoQoNVYta1xXvpEgn4lizsDkTntBpSPxJ7d9na3xOXbj3mTvNxvpu27fwE= \ No newline at end of file diff --git a/docs/read-tx.mermaid b/docs/read-tx.mermaid new file mode 100644 index 0000000..5e28ada --- /dev/null +++ b/docs/read-tx.mermaid @@ -0,0 +1,9 @@ +sequenceDiagram + participant Client + participant Quorum + participant Crux + +Client->>Quorum: getQuorumPayload(transactionHash, to) +Quorum->>Crux: receive(transactionHash, to) +Crux-->>Quorum: receiveResponse(transaction) +Quorum->>Client: transaction diff --git a/docs/read-tx.svg b/docs/read-tx.svg new file mode 100644 index 0000000..9bf9aa8 --- /dev/null +++ b/docs/read-tx.svg @@ -0,0 +1,350 @@ +ClientQuorumCruxgetQuorumPayload(transactionHash, to)receive(transactionHash, to)receiveResponse(transaction)transactionClientQuorumCrux \ No newline at end of file diff --git a/enclave/enclave.go b/enclave/enclave.go index dc0031c..adfc1d4 100644 --- a/enclave/enclave.go +++ b/enclave/enclave.go @@ -1,47 +1,53 @@ +// Package enclave provides enclaves for the secure storage and propagation of transactions. package enclave import ( "bytes" "crypto/rand" - "encoding/json" "encoding/base64" "encoding/hex" + "encoding/json" "errors" "fmt" - "io/ioutil" - "path/filepath" - "strings" + "github.com/blk-io/crux/api" + "github.com/blk-io/crux/storage" + "github.com/blk-io/crux/utils" "github.com/kevinburke/nacl" "github.com/kevinburke/nacl/box" "github.com/kevinburke/nacl/secretbox" log "github.com/sirupsen/logrus" - "github.com/blk-io/crux/storage" - "github.com/blk-io/crux/api" - "github.com/blk-io/crux/utils" + "io/ioutil" + "path/filepath" + "strings" ) +// SecureEnclave is the secure transaction enclave. type SecureEnclave struct { - Db storage.DataStore - PubKeys []nacl.Key - PrivKeys []nacl.Key - selfPubKey nacl.Key - PartyInfo api.PartyInfo - keyCache map[nacl.Key]map[nacl.Key]nacl.Key // maps sender -> recipient -> shared key - client utils.HttpClient + Db storage.DataStore // The underlying key-value datastore for encrypted transactions + PubKeys []nacl.Key // Public keys associated with this enclave + PrivKeys []nacl.Key // Private keys associated with this enclave + selfPubKey nacl.Key // An ephemeral key used for transactions only intended for this enclave + PartyInfo api.PartyInfo // Details of all other nodes (or parties) on the network + keyCache map[nacl.Key]map[nacl.Key]nacl.Key // Maps sender -> recipient -> shared key + client utils.HttpClient // The underlying HTTP client used to propagate requests + grpc bool } +// Init creates a new instance of the SecureEnclave. func Init( db storage.DataStore, pubKeyFiles, privKeyFiles []string, pi api.PartyInfo, - client utils.HttpClient) *SecureEnclave { + client utils.HttpClient, grpc bool) *SecureEnclave { + // Key format: // BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= pubKeys, err := loadPubKeys(pubKeyFiles) if err != nil { log.Fatalf("Unable to load public key files: %s, error: %v", pubKeyFiles, err) } + // Key format: // {"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"} privKeys, err := loadPrivKeys(privKeyFiles) if err != nil { @@ -49,13 +55,34 @@ func Init( } enc := SecureEnclave{ - Db : db, - PubKeys: pubKeys, - PrivKeys: privKeys, + Db: db, + PubKeys: pubKeys, + PrivKeys: privKeys, PartyInfo: pi, - client: client, + client: client, + grpc: grpc, } + // We use shared keys for encrypting data. The keys between a specific sender and recipient are + // computed once for each unique pair. + // + // Encrypt scenarios: + // The sender value must always be a public key that we have the corresponding private key for + // privateFor: [] => encrypt with sharedKey [self-private, selfPub-public] + // store in cache as (self-public, selfPub-public) + // privateFor: [recipient1, ...] => encrypt with sharedKey1 [self-private, recipient1-public], ... + // store in cache as (self-public, recipient1-public) + // Decrypt scenarios: + // epl, [] => The payload was pushed to us (we are recipient1), decrypt with sharedKey + // [recipient1-private, sender-public] + // lookup in cache as (recipient1-public, sender-public) + // epl, [recipient1, ...,] => The payload originated with us (we are self), decrypt with + // sharedKey [self-private, recipient1-public] + // lookup in cache as (self-public, recipient1-public) + // + // Note that sharedKey(privA, pubB) produces the same key as sharedKey(pubA, privB), which is + // why when sending to ones self we encrypt with sharedKey [self-private, selfPub-public], then + // retrieve with sharedKey [self-private, selfPub-public] enc.keyCache = make(map[nacl.Key]map[nacl.Key]nacl.Key) enc.selfPubKey = nacl.NewKey() @@ -71,61 +98,55 @@ func Init( enc.resolveSharedKey(enc.PrivKeys[0], pubKey, enc.selfPubKey) } - // We use shared keys for encrypting data. The keys between a specific sender and recipient are - // computed once for each unique pair. - // - // Encrypt scenarios: - // The sender value must always be a public key that we have the corresponding private key for - // privateFor: [] => encrypt with sharedKey [self-private, selfPub-public] - // store in cache as (self-public, selfPub-public) - // privateFor: [recipient1, ...] => encrypt with sharedKey1 [self-private, recipient1-public], ... - // store in cache as (self-public, recipient1-public) - // Decrypt scenarios: - // epl, [] => The payload was pushed to us (we are recipient1), decrypt with sharedKey [recipient1-private, sender-public] - // lookup in cache as (recipient1-public, sender-public) - // epl, [recipient1, ...,] => The payload originated with us (we are self), decrypt with sharedKey [self-private, recipient1-public] - // lookup in cache as (self-public, recipient1-public) - // - // Note that sharedKey(privA, pubB) produces the same key as sharedKey(pubA, privB), which is why - // when sending to ones self we encrypt with sharedKey [self-private, selfPub-public], then - // retrieve with sharedKey [self-private, selfPub-public] return &enc } +// Store a payload submitted via an Ethereum node. +// This function encrypts the payload, and distributes the encrypted payload to the other +// specified recipients in the network. +// The hash of the encrypted payload is returned to the sender. func (s *SecureEnclave) Store( message *[]byte, sender []byte, recipients [][]byte) ([]byte, error) { - var err error - var senderPubKey, senderPrivKey nacl.Key - - if len(sender) == 0 { - // from address is either default or specified on communication - senderPubKey = s.PubKeys[0] - senderPrivKey = s.PrivKeys[0] - } else { - senderPubKey, err = utils.ToKey(sender) - if err != nil { - log.WithField("senderPubKey", sender).Errorf( - "Unable to load sender public key, %v", err) - return nil, err - } + var err error + var senderPubKey, senderPrivKey nacl.Key - senderPrivKey, err = s.resolvePrivateKey(senderPubKey) - if err != nil { - log.WithField("senderPubKey", sender).Errorf( - "Unable to locate private key for sender public key, %v", err) - return nil, err - } + if len(sender) == 0 { + // from address is either default or specified on communication + senderPubKey = s.PubKeys[0] + senderPrivKey = s.PrivKeys[0] + } else { + senderPubKey, err = utils.ToKey(sender) + if err != nil { + log.WithField("senderPubKey", sender).Errorf( + "Unable to load sender public key, %v", err) + return nil, err } - return s.store(message, senderPubKey, senderPrivKey, recipients) + senderPrivKey, err = s.resolvePrivateKey(senderPubKey) + if err != nil { + log.WithField("senderPubKey", sender).Errorf( + "Unable to locate private key for sender public key, %v", err) + return nil, err + } } + return s.store(message, senderPubKey, senderPrivKey, recipients) +} + func (s *SecureEnclave) store( message *[]byte, senderPubKey, senderPrivKey nacl.Key, recipients [][]byte) ([]byte, error) { + var toSelf bool + if len(recipients) == 0 { + toSelf = true + recipients = [][]byte{(*s.selfPubKey)[:]} + } else { + toSelf = false + } + epl, masterKey := createEncryptedPayload(message, senderPubKey, recipients) for i, recipient := range recipients { @@ -150,26 +171,6 @@ func (s *SecureEnclave) store( epl.RecipientBoxes[i] = sealedBox } - var toSelf bool - if len(recipients) == 0 { - toSelf = true - recipients = [][]byte{(*s.selfPubKey)[:]} - } else { - toSelf = false - } - - // store locally - recipientKey, err := utils.ToKey(recipients[0]) - if err != nil { - log.WithField("recipientKey", recipientKey).Errorf( - "Unable to load recipient, %v", err) - } - - sharedKey := s.resolveSharedKey(senderPrivKey, senderPubKey, recipientKey) - - sealedBox := sealPayload(epl.RecipientNonce, masterKey, sharedKey) - epl.RecipientBoxes = [][]byte{ sealedBox } - encodedEpl := api.EncodePayloadWithRecipients(epl, recipients) digest, err := s.storePayload(epl, encodedEpl) @@ -184,7 +185,7 @@ func (s *SecureEnclave) store( } log.WithFields(log.Fields{ - "recipient": hex.EncodeToString(recipient),"digest": hex.EncodeToString(digest), + "recipient": hex.EncodeToString(recipient), "digest": hex.EncodeToString(digest), }).Debug("Publishing payload") s.publishPayload(recipientEpl, recipient) @@ -203,7 +204,7 @@ func createEncryptedPayload( sealedMessage := secretbox.Seal([]byte{}, *message, nonce, masterKey) - return api.EncryptedPayload { + return api.EncryptedPayload{ Sender: senderPubKey, CipherText: sealedMessage, Nonce: nonce, @@ -222,13 +223,18 @@ func (s *SecureEnclave) publishPayload(epl api.EncryptedPayload, recipient []byt if url, ok := s.PartyInfo.GetRecipient(key); ok { encoded := api.EncodePayloadWithRecipients(epl, [][]byte{}) - api.Push(encoded, url, s.client) + if s.grpc { + api.PushGrpc(encoded, url, epl) + } else { + api.Push(encoded, url, s.client) + } } else { log.WithField("recipientKey", hex.EncodeToString(recipient)).Error("Unable to resolve host") } } -func (s *SecureEnclave) resolveSharedKey(senderPrivKey, senderPubKey, recipientPubKey nacl.Key) nacl.Key { +func (s *SecureEnclave) resolveSharedKey( + senderPrivKey, senderPubKey, recipientPubKey nacl.Key) nacl.Key { keyCache, ok := s.keyCache[senderPubKey] if !ok { @@ -255,11 +261,19 @@ func (s *SecureEnclave) resolvePrivateKey(publicKey nacl.Key) (nacl.Key, error) hex.EncodeToString((*publicKey)[:])) } +// Store a binary encoded payload within this SecureEnclave. +// This will be a payload that has been propagated to this node as it is a party on the +// transaction. I.e. it is not the original recipient of the transaction, but one of the recipients +// it is intended for. func (s *SecureEnclave) StorePayload(encoded []byte) ([]byte, error) { epl, _ := api.DecodePayloadWithRecipients(encoded) return s.storePayload(epl, encoded) } +func (s *SecureEnclave) StorePayloadGrpc(epl api.EncryptedPayload, encoded []byte) ([]byte, error) { + return s.storePayload(epl, encoded) +} + func (s *SecureEnclave) storePayload(epl api.EncryptedPayload, encoded []byte) ([]byte, error) { digestHash := utils.Sha3Hash(epl.CipherText) err := s.Db.Write(&digestHash, &encoded) @@ -278,12 +292,17 @@ func sealPayload( sharedKey) } +// RetrieveDefault is used to retrieve the provided payload. It attempts to use a default key +// value of the first public key associated with this SecureEnclave instance. +// If the payload cannot be found, or decrypted successfully an error is returned. func (s *SecureEnclave) RetrieveDefault(digestHash *[]byte) ([]byte, error) { // to address is either default or specified on communication key := (*s.PubKeys[0])[:] return s.Retrieve(digestHash, &key) } +// Retrieve is used to retrieve the provided payload. +// If the payload cannot be found, or decrypted successfully an error is returned. func (s *SecureEnclave) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) { encoded, err := s.Db.Read(digestHash) @@ -336,6 +355,8 @@ func (s *SecureEnclave) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) return payload, nil } +// RetrieveFor retrieves a payload with the given digestHash for a specific recipient who was one +// of the original recipients specified on the payload. func (s *SecureEnclave) RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (*[]byte, error) { encoded, err := s.Db.Read(digestHash) if err != nil { @@ -357,9 +378,12 @@ func (s *SecureEnclave) RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (* return &encoded, nil } } - return nil, fmt.Errorf("invalid recipient %q requested for payload", reqRecipient) + return nil, fmt.Errorf("invalid recipient %x requested for payload", reqRecipient) } +// RetrieveAllFor retrieves all payloads that the specified recipient was an original recipient +// for. +// Each payload found is published to the specified recipient. func (s *SecureEnclave) RetrieveAllFor(reqRecipient *[]byte) error { return s.Db.ReadAll(func(key, value *[]byte) { epl, recipients := api.DecodePayloadWithRecipients(*value) @@ -381,18 +405,38 @@ func (s *SecureEnclave) RetrieveAllFor(reqRecipient *[]byte) error { }) } +// Delete deletes the payload associated with the given digestHash from the SecureEnclave's store. func (s *SecureEnclave) Delete(digestHash *[]byte) error { return s.Db.Delete(digestHash) } +// UpdatePartyInfo applies the provided binary encoded party details to the SecureEnclave's +// own party details store. func (s *SecureEnclave) UpdatePartyInfo(encoded []byte) { s.PartyInfo.UpdatePartyInfo(encoded) } +func (s *SecureEnclave) UpdatePartyInfoGrpc(url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool) { + s.PartyInfo.UpdatePartyInfoGrpc(url, recipients, parties) +} + +// GetEncodedPartyInfo provides this SecureEnclaves PartyInfo details in a binary encoded format. func (s *SecureEnclave) GetEncodedPartyInfo() []byte { return api.EncodePartyInfo(s.PartyInfo) } +func (s *SecureEnclave) GetEncodedPartyInfoGrpc() []byte { + encoded, err := json.Marshal(api.PartyInfoResponse{Payload: api.EncodePartyInfo(s.PartyInfo)}) + if err != nil { + log.Errorf("Marshalling failed %v", err) + } + return encoded +} + +func (s *SecureEnclave) GetPartyInfo() (string, map[[nacl.KeySize]byte]string, map[string]bool) { + return s.PartyInfo.GetAllValues() +} + func loadPubKeys(pubKeyFiles []string) ([]nacl.Key, error) { return loadKeys( pubKeyFiles, @@ -444,6 +488,9 @@ func loadKeys( return keys, nil } +// DoKeyGeneration is used to generate new public and private key-pairs, writing them to the +// provided file locations. +// Public keys have the "pub" suffix, whereas private keys have the "key" suffix. func DoKeyGeneration(keyFile string) error { pubKey, privKey, err := box.GenerateKey(rand.Reader) if err != nil { @@ -458,7 +505,7 @@ func DoKeyGeneration(keyFile string) error { b64PubKey := base64.StdEncoding.EncodeToString((*pubKey)[:]) b64PrivKey := base64.StdEncoding.EncodeToString((*privKey)[:]) - err = ioutil.WriteFile(keyFile + ".pub", []byte(b64PubKey), 0600) + err = ioutil.WriteFile(keyFile+".pub", []byte(b64PubKey), 0600) if err != nil { return fmt.Errorf("unable to write public key: %s, error: %v", keyFile, err) } @@ -476,7 +523,7 @@ func DoKeyGeneration(keyFile string) error { return fmt.Errorf("unable to encode private key: %v, error: %v", jsonKey, err) } - err = ioutil.WriteFile(keyFile, encoded, 0600) + err = ioutil.WriteFile(keyFile+".key", encoded, 0600) if err != nil { return fmt.Errorf("unable to write private key: %s, error: %v", keyFile, err) } diff --git a/enclave/enclave_test.go b/enclave/enclave_test.go index 46efee6..10ea595 100644 --- a/enclave/enclave_test.go +++ b/enclave/enclave_test.go @@ -2,6 +2,10 @@ package enclave import ( "bytes" + "github.com/blk-io/crux/api" + "github.com/blk-io/crux/storage" + "github.com/blk-io/crux/utils" + "github.com/kevinburke/nacl" "io/ioutil" "net/http" "os" @@ -9,17 +13,13 @@ import ( "sync" "testing" "time" - "github.com/blk-io/crux/storage" - "github.com/blk-io/crux/api" - "github.com/blk-io/crux/utils" - "github.com/kevinburke/nacl" ) var message = []byte("Test message") type MockClient struct { serviceMu sync.Mutex - requests [][]byte + requests [][]byte } func (c *MockClient) Do(req *http.Request) (*http.Response, error) { @@ -58,7 +58,7 @@ func initEnclave( []string{"testdata/key.pub"}, []string{"testdata/key"}, pi, - client) + client, false) } func initDefaultEnclave(t *testing.T, @@ -68,7 +68,7 @@ func initDefaultEnclave(t *testing.T, client = &MockClient{} pi := api.InitPartyInfo( "http://localhost:8000", - []string{"http://localhost:8001"}, client) + []string{"http://localhost:8001"}, client, false) return initEnclave(t, dbPath, pi, client) } @@ -111,7 +111,7 @@ func TestStoreAndRetrieve(t *testing.T) { if !bytes.Equal(message, returned) { t.Errorf( - "Retrieved message is not the same as original:\n" + + "Retrieved message is not the same as original:\n"+ "Original: %v\nRetrieved: %v", message, returned) } @@ -146,7 +146,7 @@ func TestStoreAndRetrieve(t *testing.T) { []string{"testdata/rcpt1.pub"}, []string{"testdata/rcpt1"}, pi, - client) + client, false) var digest2 []byte digest2, err = enc2.StorePayload(propagatedPl) @@ -162,7 +162,7 @@ func TestStoreAndRetrieve(t *testing.T) { if !bytes.Equal(message, returned2) { t.Errorf( - "Retrieved message is not the same as original:\n" + + "Retrieved message is not the same as original:\n"+ "Original: %v\nRetrieved: %v", message, returned) } @@ -189,7 +189,7 @@ func TestStoreAndRetrieveSelf(t *testing.T) { if !bytes.Equal(message, returned) { t.Errorf( - "Retrieved message is not the same as original:\n" + + "Retrieved message is not the same as original:\n"+ "Original: %v\nRetrieved: %v", message, returned) } @@ -270,7 +270,7 @@ func TestRetrieveNotAuthorised(t *testing.T) { if !bytes.Equal(message, returned) { t.Errorf( - "Retrieved message is not the same as original:\n" + + "Retrieved message is not the same as original:\n"+ "Original: %v\nRetrieved: %v", message, returned) } @@ -297,7 +297,7 @@ func TestDelete(t *testing.T) { if !bytes.Equal(message, returned) { t.Errorf( - "Retrieved message is not the same as original:\n" + + "Retrieved message is not the same as original:\n"+ "Original: %v\nRetrieved: %v", message, returned) } @@ -445,7 +445,7 @@ func TestDoKeyGeneration(t *testing.T) { t.Fatal(err) } - _, err = loadPrivKeys([]string{keyFiles}) + _, err = loadPrivKeys([]string{keyFiles + ".key"}) if err != nil { t.Fatal(err) } diff --git a/enclave/testdata/cert/server.crt b/enclave/testdata/cert/server.crt new file mode 100644 index 0000000..283b8a1 --- /dev/null +++ b/enclave/testdata/cert/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJDCCAgwCCQDmw4WXCHzRUTANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJV +SzEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25kb24xDzANBgNVBAoMBmJs +ay1pbzESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTE4MDYxNDE0Mzg1NFoXDTI4MDYx +MTE0Mzg1NFowVDELMAkGA1UEBhMCVUsxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UE +BwwGTG9uZG9uMQ8wDQYDVQQKDAZibGstaW8xEjAQBgNVBAMMCTEyNy4wLjAuMTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALSS/JHKnE9f9pU7jw3TZint +6aaj7jSEzV5BArjBs0GzDk/R+55AvE+428iFTywiAdeUBO3xxxEzCZ8OOLkffJ95 +Vza0giWcR3/+F9na+1kCOuc2H/fiIvB8DMzGQxjukVLdIaCjl/F5Uyn7gKYKb7Tr +cryf/HkaaT3eA4TahC1dOhhq4q/WmkaWariM9QlFyJDfvYiL1XkmOuiiC6vD+yvd +71dBtugDpIcNgmAZ+rVn4ZoC3Tc16OzeqrvxquF1YO0QakGqiGskZA6NTm+VL/Gc +ubnFweKXkrvisTcqUleoGQM9TibbVQf/A0DamAVrauRs6rxva5JqW7Gh4o5dBUcC +AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYHeXraxdqSm5PTYrWqVks+5ctTv/tO4w +cEtleZHy86qnmtu8gqHH+tAKVjn/JNGvYNX/M0oMPjxn0LffGCH6MhC9072RDA10 +XC+yUQx2tm5Q8RRVjb/S+6GczAqXVE6Qc59zgTaFtE8xc/J/wW6XPhJ99Jly2oba +y/6UMvPfCLtf7pA99V4lXat0oRYtVbFkyxIGVl0Gx2COJx65UUtUs+Grf41CQCYr +VXSUTl6wTNj6llAMDkgyFobmfwWzr0DWJJSDycHuC5oxdIe6nhLRq1EgU5DHwyGD +Mz9tLPJHGpJPX8ml4U/XuiYHNDt5hJYZ0veJ6fFM3iSBMCfBIh4Npg== +-----END CERTIFICATE----- diff --git a/enclave/testdata/cert/server.csr b/enclave/testdata/cert/server.csr new file mode 100644 index 0000000..85379c9 --- /dev/null +++ b/enclave/testdata/cert/server.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICsTCCAZkCAQAwVDELMAkGA1UEBhMCVUsxDzANBgNVBAgMBkxvbmRvbjEPMA0G +A1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZibGstaW8xEjAQBgNVBAMMCTEyNy4wLjAu +MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALSS/JHKnE9f9pU7jw3T +Zint6aaj7jSEzV5BArjBs0GzDk/R+55AvE+428iFTywiAdeUBO3xxxEzCZ8OOLkf +fJ95Vza0giWcR3/+F9na+1kCOuc2H/fiIvB8DMzGQxjukVLdIaCjl/F5Uyn7gKYK +b7Trcryf/HkaaT3eA4TahC1dOhhq4q/WmkaWariM9QlFyJDfvYiL1XkmOuiiC6vD ++yvd71dBtugDpIcNgmAZ+rVn4ZoC3Tc16OzeqrvxquF1YO0QakGqiGskZA6NTm+V +L/GcubnFweKXkrvisTcqUleoGQM9TibbVQf/A0DamAVrauRs6rxva5JqW7Gh4o5d +BUcCAwEAAaAYMBYGCSqGSIb3DQEJBzEJDAdibGtjcnV4MA0GCSqGSIb3DQEBCwUA +A4IBAQBYRPFuhVoNhYBVoO4qaNet7W4JAZ2YkpTdE/SdECyBAt4lX4OPV+lFYrUD +AjPf98IjwVwNijz6DboqEx+1rUKzLEV/Jv8k0O8noJDR3fRkFU3cyIDcW/N9Wwcc +p3qHwRuSvv+QmDoikJeCR8iN61jeo6WG2KKB4GUETK35SMDS31ExkWHNa/WIFzrV +MTB/Vy2yH0xgxhDRa8sEbsGrvf/XnhKKGNbffmTp46dBItBkDKeiHh2FGI6F8CBv +bkhB0/7fC8Hi4aoBMSdT88L8Y7+mVMNfybmCuSr1foK+gMSS7UweLn2oPEMohFZW +4mkS8TD1bUkavlCrhVuExCSLVXLu +-----END CERTIFICATE REQUEST----- diff --git a/enclave/testdata/cert/server.key b/enclave/testdata/cert/server.key new file mode 100644 index 0000000..9592691 --- /dev/null +++ b/enclave/testdata/cert/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtJL8kcqcT1/2lTuPDdNmKe3ppqPuNITNXkECuMGzQbMOT9H7 +nkC8T7jbyIVPLCIB15QE7fHHETMJnw44uR98n3lXNrSCJZxHf/4X2dr7WQI65zYf +9+Ii8HwMzMZDGO6RUt0hoKOX8XlTKfuApgpvtOtyvJ/8eRppPd4DhNqELV06GGri +r9aaRpZquIz1CUXIkN+9iIvVeSY66KILq8P7K93vV0G26AOkhw2CYBn6tWfhmgLd +NzXo7N6qu/Gq4XVg7RBqQaqIayRkDo1Ob5Uv8Zy5ucXB4peSu+KxNypSV6gZAz1O +JttVB/8DQNqYBWtq5GzqvG9rkmpbsaHijl0FRwIDAQABAoIBAFx/iteXxRg33RU2 +zCHV71h9IZoWEThf6t9kR+OifZOjCCrFMrIvEQd5d0QxXYwK44ytqxTELCfYUA5k +95OE2I7MVmuUbbKcalfbLhaPwP8oUEoOBLQy55juwpPG36oO0uxyj/48ruGoN3yi +85SadfxkO4L6JRdX+x8Q8haE5P5jDUZt/lol1neQR8moEpKwy+KhAEPBe3c+MlXl +wHWYk/j8GHnfgcDCLITpu7dHGUDye7l0dbM4EjbU9R3vcsTfT18eekUqm6MER0Gd +wA4bcrAhlNQjNyLocmSMgzD3NjeXzbbA4NXO32X6OmH+I+sGPEJHcZU0Kew0Depe +yhkmXKkCgYEA5pRIhouqF3RzbGf+yMMdo9bQc7U4iUhTcZ9Vm/z/Nxs0d1pCHy5L +n3VAgOSsIb8AP/DmhpRGyBAGodKnkpXrvMG03y7Pf2KjOxh/A2thTDeB4dcelEvu +Q9Uj1s/BctyVzelrL0DZjBhwxBuT56lqq56E8Yb/jn7yZZ8g7A7vetMCgYEAyHtk +qKfh3d4oF5/mro9KmWsmjB/jfSwXJzu9UsdmLFlRhsRZBXLYxwLxtAFzcM+y+ac3 +18dowvBD2p3tvEwKVF0ncxn0vDNDF8n+sYlVsBrLuFccfw1bgl3jmmAhuXX6NZO8 +HcO/S6PjrnSKYd4WXRjHqiv5ufMZFQ1OKjz1mz0CgYEAmOi7E+ao3LcQGFL65p5m +GJHLWQBTxs6c75uvhSuJAD1dVM0ZTl5ALjXumcuLzzE/9CdIaPUJ34CpNUVidVZQ +p7N5xAvh9OMvxm/fQyBBvO6OhntHPyb/kiJViw3phsd73Lqvpv2Fh19p4NM9CYMT +R05vcVCKRzAuhW+6wHDDJZsCgYEAxptXEK2f2Efot960jGFvqaS4v0AoASzYkwlf +eM4Irg6d8UA6YGdx0VVdVNHLJwrbZu79J0powhV7YuvpRAygfwr5tdEU3gx6fuqg +4ggHVzp0bt39YPA+o1uXyqtJPY1eng0I4wO0Up69Q2o4XNPCm9+cjTybXFczleNk +d/uD5JECgYBcwAPEFNU2+NcU+mYWFHIvNK4hVLD/Bfr6EmkJcOLYE+BZPdaWaPGv +ZE9ZgLpf6bB9cmQGUifWdSHDXwQH7kpYKS5pJdGsPbuFys5HU4xCbCEErsRnPKlE +K/hcgeDuKNJ6/nV1ypUsOXpO6ukhLnTvj1R9MQVipprTaQbxRBxx8g== +-----END RSA PRIVATE KEY----- diff --git a/server/proto_server.go b/server/proto_server.go new file mode 100644 index 0000000..01720f4 --- /dev/null +++ b/server/proto_server.go @@ -0,0 +1,134 @@ +package server + +import ( + "fmt" + "github.com/blk-io/chimera-api/chimera" + "github.com/blk-io/crux/utils" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "net" + "net/http" +) + +func (tm *TransactionManager) startRpcServer(networkInterface string, port int, grpcJsonPort int, ipcPath string, tls bool, certFile, keyFile string) error { + lis, err := utils.CreateIpcSocket(ipcPath) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := Server{Enclave: tm.Enclave} + grpcServer := grpc.NewServer() + chimera.RegisterClientServer(grpcServer, &s) + go func() { + log.Fatal(grpcServer.Serve(lis)) + }() + + go func() error { + var err error + if tls { + err = tm.startRestServerTLS(networkInterface, port, certFile, keyFile, certFile) + } else { + err = tm.startRestServer(networkInterface, port) + } + if grpcJsonPort != -1 { + if tls { + err = tm.startJsonServerTLS(networkInterface, port, grpcJsonPort, certFile, keyFile, certFile) + } else { + err = tm.startJsonServer(networkInterface, port, grpcJsonPort) + } + } + if err != nil { + log.Fatalf("failed to start gRPC REST server: %s", err) + } + return err + }() + + return err +} + +func (tm *TransactionManager) startJsonServer(networkInterface string, port int, grpcJsonPort int) error { + address := fmt.Sprintf("%s:%d", networkInterface, grpcJsonPort) + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + mux := runtime.NewServeMux() + opts := []grpc.DialOption{grpc.WithInsecure()} + err := chimera.RegisterClientHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", networkInterface, port), opts) + if err != nil { + return fmt.Errorf("could not register service: %s", err) + } + log.Printf("starting HTTP/1.1 REST server on %s", address) + err = http.ListenAndServe(address, mux) + if err != nil { + return fmt.Errorf("could not listen on %s due to: %s", address, err) + } + return nil +} + +func (tm *TransactionManager) startRestServer(networkInterface string, port int) error { + grpcAddress := fmt.Sprintf("%s:%d", networkInterface, port) + lis, err := net.Listen("tcp", grpcAddress) + if err != nil { + panic(err) + } + s := Server{Enclave: tm.Enclave} + grpcServer := grpc.NewServer() + chimera.RegisterClientServer(grpcServer, &s) + go func() { + log.Fatal(grpcServer.Serve(lis)) + }() + return nil +} + +func (tm *TransactionManager) startJsonServerTLS(networkInterface string, port int, grpcJsonPort int, certFile, keyFile, ca string) error { + address := fmt.Sprintf("%s:%d", networkInterface, grpcJsonPort) + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + mux := runtime.NewServeMux() + creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) + err = chimera.RegisterClientHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", networkInterface, port), []grpc.DialOption{grpc.WithTransportCredentials(creds)}) + if err != nil { + log.Fatalf("could not register service Ping: %s", err) + return err + } + http.ListenAndServe(address, mux) + log.Printf("started HTTPS REST server on %s", address) + return nil +} + +func (tm *TransactionManager) startRestServerTLS(networkInterface string, port int, certFile, keyFile, ca string) error { + grpcAddress := fmt.Sprintf("%s:%d", networkInterface, port) + lis, err := net.Listen("tcp", grpcAddress) + if err != nil { + log.Fatalf("failed to start gRPC REST server: %s", err) + } + s := Server{Enclave: tm.Enclave} + creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) + opts := []grpc.ServerOption{grpc.Creds(creds)} + if err != nil { + log.Fatalf("failed to load credentials : %v", err) + } + grpcServer := grpc.NewServer(opts...) + chimera.RegisterClientServer(grpcServer, &s) + go func() { + log.Fatal(grpcServer.Serve(lis)) + }() + return nil +} + +func GetFreePort(networkInterface string) (int, error) { + addr, err := net.ResolveTCPAddr("tcp", networkInterface + ":0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} diff --git a/server/server.go b/server/server.go index 4d45268..a7a7242 100644 --- a/server/server.go +++ b/server/server.go @@ -1,22 +1,27 @@ +// Package server contains the core server components. package server import ( "encoding/base64" + "encoding/hex" "encoding/json" "fmt" - "io/ioutil" - "net/http" - "strconv" "github.com/blk-io/crux/api" "github.com/blk-io/crux/utils" + "github.com/kevinburke/nacl" log "github.com/sirupsen/logrus" - "encoding/hex" - "net/textproto" + "io/ioutil" + "net/http" "net/http/httputil" + "net/textproto" + "os" + "strconv" ) +// Enclave is the interface used by the transaction enclaves. type Enclave interface { Store(message *[]byte, sender []byte, recipients [][]byte) ([]byte, error) + StorePayloadGrpc(epl api.EncryptedPayload, encoded []byte) ([]byte, error) StorePayload(encoded []byte) ([]byte, error) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) RetrieveDefault(digestHash *[]byte) ([]byte, error) @@ -24,9 +29,13 @@ type Enclave interface { RetrieveAllFor(reqRecipient *[]byte) error Delete(digestHash *[]byte) error UpdatePartyInfo(encoded []byte) + UpdatePartyInfoGrpc(url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool) GetEncodedPartyInfo() []byte + GetEncodedPartyInfoGrpc() []byte + GetPartyInfo() (url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool) } +// TransactionManager is responsible for handling all transaction requests. type TransactionManager struct { Enclave Enclave } @@ -65,9 +74,21 @@ func requestLogger(handler http.Handler) http.Handler { }) } -func Init(enc Enclave, port int, ipcPath string) (TransactionManager, error) { - tm := TransactionManager{Enclave : enc} +// Init initializes a new TransactionManager instance. +func Init(enc Enclave, networkInterface string, port int, ipcPath string, grpc bool, grpcJsonPort int, tls bool, certFile, keyFile string) (TransactionManager, error) { + tm := TransactionManager{Enclave: enc} + var err error + if grpc == true { + err = tm.startRpcServer(networkInterface, port, grpcJsonPort, ipcPath, tls, certFile, keyFile) + + } else { + err = tm.startHttpserver(networkInterface, port, ipcPath, tls, certFile, keyFile) + } + + return tm, err +} +func (tm *TransactionManager) startHttpserver(networkInterface string, port int, ipcPath string, tls bool, certFile, keyFile string) error { httpServer := http.NewServeMux() httpServer.HandleFunc(upCheck, tm.upcheck) httpServer.HandleFunc(version, tm.version) @@ -75,11 +96,22 @@ func Init(enc Enclave, port int, ipcPath string) (TransactionManager, error) { httpServer.HandleFunc(resend, tm.resend) httpServer.HandleFunc(partyInfo, tm.partyInfo) - serverUrl := "localhost:" + strconv.Itoa(port) - go func() { - log.Fatal(http.ListenAndServe(serverUrl, requestLogger(httpServer))) - }() - log.Infof("HTTP server is running at: %s", serverUrl) + serverUrl := networkInterface + ":" + strconv.Itoa(port) + if tls { + err := CheckCertFiles(certFile, keyFile) + if err != nil { + log.Fatal(err) + } + go func() { + log.Fatal(http.ListenAndServeTLS(serverUrl, certFile, keyFile, requestLogger(httpServer))) + }() + log.Infof("HTTPS server is running at: %s", serverUrl) + } else { + go func() { + log.Fatal(http.ListenAndServe(serverUrl, requestLogger(httpServer))) + }() + log.Infof("HTTP server is running at: %s", serverUrl) + } // Restricted to IPC ipcServer := http.NewServeMux() @@ -92,11 +124,24 @@ func Init(enc Enclave, port int, ipcPath string) (TransactionManager, error) { ipcServer.HandleFunc(delete, tm.delete) ipc, err := utils.CreateIpcSocket(ipcPath) + if err != nil { + log.Fatalf("Failed to start IPC Server at %s", ipcPath) + } go func() { log.Fatal(http.Serve(ipc, requestLogger(ipcServer))) }() log.Infof("IPC server is running at: %s", ipcPath) - return tm, err + + return err +} + +func CheckCertFiles(certFile, keyFile string) error { + if _, err := os.Stat(certFile); os.IsNotExist(err) { + return err + } else if _, err := os.Stat(keyFile); os.IsNotExist(err) { + return err + } + return nil } func (s *TransactionManager) upcheck(w http.ResponseWriter, req *http.Request) { @@ -164,12 +209,12 @@ func (s *TransactionManager) sendRaw(w http.ResponseWriter, req *http.Request) { return } - // Uncomment the below for Quorum v2.0.2 onwards + // Uncomment the below for Quorum v2.0.1 or below // see https://github.com/jpmorganchase/quorum/commit/ee498061b5a74bf1f3290139a53840345fa038cb#diff-63fbbd6b2c0487b8cd4445e881822cdd - // encodedKey := base64.StdEncoding.EncodeToString(key) - // fmt.Fprint(w, encodedKey) - // Then delete this line - w.Write(key) + //w.Write(key) + // Then delete the below lines + encodedKey := base64.StdEncoding.EncodeToString(key) + fmt.Fprint(w, encodedKey) } func (s *TransactionManager) processSend( @@ -179,9 +224,9 @@ func (s *TransactionManager) processSend( payload *[]byte) ([]byte, error) { log.WithFields(log.Fields{ - "b64From" : b64from, + "b64From": b64from, "b64Recipients": b64recipients, - "payload": hex.EncodeToString(*payload),}).Debugf( + "payload": hex.EncodeToString(*payload)}).Debugf( "Processing send request") sender, err := base64.StdEncoding.DecodeString(b64from) @@ -376,4 +421,3 @@ func internalServerError(w http.ResponseWriter, message string) { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, message) } - diff --git a/server/server_handler.go b/server/server_handler.go new file mode 100644 index 0000000..cd6fb61 --- /dev/null +++ b/server/server_handler.go @@ -0,0 +1,163 @@ +package server + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/blk-io/chimera-api/chimera" + "github.com/blk-io/crux/api" + "github.com/kevinburke/nacl" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" +) + +type Server struct { + Enclave Enclave +} + +func (s *Server) Version(ctx context.Context, in *chimera.ApiVersion) (*chimera.ApiVersion, error) { + return &chimera.ApiVersion{Version: apiVersion}, nil +} + +func (s *Server) Upcheck(ctx context.Context, in *chimera.UpCheckResponse) (*chimera.UpCheckResponse, error) { + return &chimera.UpCheckResponse{Message: upCheckResponse}, nil +} +func (s *Server) Send(ctx context.Context, in *chimera.SendRequest) (*chimera.SendResponse, error) { + key, err := s.processSend(in.GetFrom(), in.GetTo(), &in.Payload) + var sendResp chimera.SendResponse + if err != nil { + log.Error(err) + } else { + sendResp = chimera.SendResponse{Key: key} + } + return &sendResp, err +} + +func (s *Server) processSend(b64from string, b64recipients []string, payload *[]byte) ([]byte, error) { + log.WithFields(log.Fields{ + "b64From": b64from, + "b64Recipients": b64recipients, + "payload": hex.EncodeToString(*payload)}).Debugf( + "Processing send request") + + sender, err := base64.StdEncoding.DecodeString(b64from) + if err != nil { + decodeErrorGRPC("sender", b64from, err) + return nil, err + } + + recipients := make([][]byte, len(b64recipients)) + for i, value := range b64recipients { + recipient, err := base64.StdEncoding.DecodeString(value) + if err != nil { + decodeErrorGRPC("recipients", value, err) + return nil, err + } else { + recipients[i] = recipient + } + } + + return s.Enclave.Store(payload, sender, recipients) +} + +func (s *Server) Receive(ctx context.Context, in *chimera.ReceiveRequest) (*chimera.ReceiveResponse, error) { + payload, err := s.processReceive(in.Key, in.To) + var receiveResp chimera.ReceiveResponse + if err != nil { + log.Error(err) + } else { + receiveResp = chimera.ReceiveResponse{Payload: payload} + } + return &receiveResp, err +} + +func (s *Server) processReceive(b64Key []byte, b64To string) ([]byte, error) { + if b64To != "" { + to, err := base64.StdEncoding.DecodeString(b64To) + if err != nil { + return nil, fmt.Errorf("unable to decode to: %s", b64Key) + } + + return s.Enclave.Retrieve(&b64Key, &to) + } else { + return s.Enclave.RetrieveDefault(&b64Key) + } +} + +func (s *Server) UpdatePartyInfo(ctx context.Context, in *chimera.PartyInfo) (*chimera.PartyInfoResponse, error) { + recipients := make(map[[nacl.KeySize]byte]string) + for url, key := range in.Recipients { + var as [32]byte + copy(as[:], key) + recipients[as] = url + } + s.Enclave.UpdatePartyInfoGrpc(in.Url, recipients, in.Parties) + encoded := s.Enclave.GetEncodedPartyInfoGrpc() + var decodedPartyInfo chimera.PartyInfoResponse + err := json.Unmarshal(encoded, &decodedPartyInfo) + if err != nil { + log.Errorf("Unmarshalling failed with %v", err) + } + return &chimera.PartyInfoResponse{Payload: decodedPartyInfo.Payload}, nil +} + +func (s *Server) Push(ctx context.Context, in *chimera.PushPayload) (*chimera.PartyInfoResponse, error) { + sender := new([nacl.KeySize]byte) + nonce := new([nacl.NonceSize]byte) + recipientNonce := new([nacl.NonceSize]byte) + copy((*sender)[:], in.Ep.Sender) + copy((*nonce)[:], in.Ep.Nonce) + copy((*recipientNonce)[:], in.Ep.ReciepientNonce) + + encyptedPayload := api.EncryptedPayload{ + Sender: sender, + CipherText: in.Ep.CipherText, + Nonce: nonce, + RecipientBoxes: in.Ep.ReciepientBoxes, + RecipientNonce: recipientNonce, + } + + digestHash, err := s.Enclave.StorePayloadGrpc(encyptedPayload, in.Encoded) + if err != nil { + log.Fatalf("Unable to store payload, error: %s\n", err) + } + + return &chimera.PartyInfoResponse{Payload: digestHash}, nil +} + +func (s *Server) Delete(ctx context.Context, in *chimera.DeleteRequest) (*chimera.DeleteRequest, error) { + var deleteReq chimera.DeleteRequest + err := s.Enclave.Delete(&deleteReq.Key) + if err != nil { + log.Fatalf("Unable to delete payload, error: %s\n", err) + } + return &chimera.DeleteRequest{Key: deleteReq.Key}, nil +} + +func (s *Server) Resend(ctx context.Context, in *chimera.ResendRequest) (*chimera.ResendResponse, error) { + var resendReq chimera.ResendRequest + var err error + + if resendReq.Type == "all" { + err = s.Enclave.RetrieveAllFor(&resendReq.PublicKey) + if err != nil { + log.Fatalf("Invalid body, exited with %s", err) + } + return nil, err + } else if resendReq.Type == "individual" { + var encodedPl *[]byte + encodedPl, err = s.Enclave.RetrieveFor(&resendReq.Key, &resendReq.PublicKey) + if err != nil { + log.Fatalf("Invalid body, exited with %s", err) + return nil, err + } + return &chimera.ResendResponse{Encoded: *encodedPl}, nil + } + return nil, err +} + +func decodeErrorGRPC(name string, value string, err error) { + log.Error(fmt.Sprintf("Invalid request: unable to decode %s: %s, error: %s\n", + name, value, err)) +} diff --git a/server/server_test.go b/server/server_test.go index 6ab41ae..ecc8665 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,15 +1,24 @@ package server import ( - "testing" + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/blk-io/chimera-api/chimera" + "github.com/blk-io/crux/api" + "github.com/blk-io/crux/enclave" + "github.com/blk-io/crux/storage" + "github.com/kevinburke/nacl" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" + "io/ioutil" "net/http" "net/http/httptest" - "github.com/blk-io/crux/api" - "encoding/json" - "encoding/base64" - "bytes" + "path" "reflect" - "github.com/kevinburke/nacl" + "testing" ) const sender = "BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=" @@ -18,42 +27,55 @@ const receiver = "QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=" var payload = []byte("payload") var encodedPayload = base64.StdEncoding.EncodeToString(payload) -type MockEnclave struct {} +type MockEnclave struct{} -func (s* MockEnclave) Store(message *[]byte, sender []byte, recipients [][]byte) ([]byte, error) { +func (s *MockEnclave) Store(message *[]byte, sender []byte, recipients [][]byte) ([]byte, error) { return *message, nil } -func (s* MockEnclave) StorePayload(encoded []byte) ([]byte, error) { +func (s *MockEnclave) StorePayload(encoded []byte) ([]byte, error) { + return encoded, nil +} +func (s *MockEnclave) StorePayloadGrpc(epl api.EncryptedPayload, encoded []byte) ([]byte, error) { return encoded, nil } -func (s* MockEnclave) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) { +func (s *MockEnclave) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) { return *digestHash, nil } -func (s* MockEnclave) RetrieveDefault(digestHash *[]byte) ([]byte, error) { +func (s *MockEnclave) RetrieveDefault(digestHash *[]byte) ([]byte, error) { return *digestHash, nil } -func (s* MockEnclave) RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (*[]byte, error) { +func (s *MockEnclave) RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (*[]byte, error) { return digestHash, nil } -func (s* MockEnclave) RetrieveAllFor(reqRecipient *[]byte) error { +func (s *MockEnclave) RetrieveAllFor(reqRecipient *[]byte) error { return nil } -func (s* MockEnclave) Delete(digestHash *[]byte) error { +func (s *MockEnclave) Delete(digestHash *[]byte) error { return nil } -func (s* MockEnclave) UpdatePartyInfo(encoded []byte) {} +func (s *MockEnclave) UpdatePartyInfo(encoded []byte) {} -func (s* MockEnclave) GetEncodedPartyInfo() []byte { +func (s *MockEnclave) UpdatePartyInfoGrpc(string, map[[nacl.KeySize]byte]string, map[string]bool) {} + +func (s *MockEnclave) GetEncodedPartyInfo() []byte { return payload } +func (s *MockEnclave) GetEncodedPartyInfoGrpc() []byte { + return payload +} + +func (s *MockEnclave) GetPartyInfo() (string, map[[nacl.KeySize]byte]string, map[string]bool) { + return "", nil, nil +} + func TestUpcheck(t *testing.T) { tm := TransactionManager{} runSimpleGetRequest(t, upCheck, upCheckResponse, tm.upcheck) @@ -90,12 +112,12 @@ func TestSend(t *testing.T) { sendReqs := []api.SendRequest{ { Payload: encodedPayload, - From: sender, - To: []string{receiver}, + From: sender, + To: []string{receiver}, }, { Payload: encodedPayload, - To: []string{}, + To: []string{}, }, { Payload: encodedPayload, @@ -112,6 +134,49 @@ func TestSend(t *testing.T) { } } +func TestGRPCSend(t *testing.T) { + sendReqs := []chimera.SendRequest{ + { + Payload: payload, + From: sender, + To: []string{receiver}, + }, + { + Payload: payload, + To: []string{}, + }, + { + Payload: payload, + }, + } + expected := chimera.SendResponse{Key: payload} + + freePort, err := GetFreePort("localhost") + if err != nil { + log.Fatalf("failed to find a free port to start gRPC REST server: %s", err) + } + ipcPath := InitgRPCServer(t, true, freePort) + + var conn *grpc.ClientConn + conn, err = grpc.Dial(fmt.Sprintf("passthrough:///unix://%s", ipcPath), grpc.WithInsecure()) + if err != nil { + t.Fatalf("Connection to gRPC server failed with error %s", err) + } + defer conn.Close() + c := chimera.NewClientClient(conn) + + for _, sendReq := range sendReqs { + resp, err := c.Send(context.Background(), &sendReq) + if err != nil { + t.Fatalf("gRPC send failed with %s", err) + } + response := chimera.SendResponse{Key: resp.Key} + if !reflect.DeepEqual(response, expected) { + t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected) + } + } +} + func TestSendRaw(t *testing.T) { tm := TransactionManager{Enclave: &MockEnclave{}} @@ -119,9 +184,9 @@ func TestSendRaw(t *testing.T) { headers[hFrom] = []string{sender} headers[hTo] = []string{receiver} - // Uncomment the below for Quorum v2.0.2 onwards - //runRawHandlerTest(t, headers, payload, []byte(encodedPayload), sendRaw, tm.sendRaw) - runRawHandlerTest(t, headers, payload, payload, sendRaw, tm.sendRaw) + runRawHandlerTest(t, headers, payload, []byte(encodedPayload), sendRaw, tm.sendRaw) + // Uncomment the below for Quorum v2.0.1 or below + //runRawHandlerTest(t, headers, payload, payload, sendRaw, tm.sendRaw) } func TestReceive(t *testing.T) { @@ -129,7 +194,7 @@ func TestReceive(t *testing.T) { receiveReqs := []api.ReceiveRequest{ { Key: encodedPayload, - To: receiver, + To: receiver, }, } @@ -143,6 +208,39 @@ func TestReceive(t *testing.T) { } } +func TestGRPCReceive(t *testing.T) { + receiveReqs := []chimera.ReceiveRequest{ + { + Key: payload, + To: receiver, + }, + } + expected := chimera.ReceiveResponse{Payload: payload} + freePort, err := GetFreePort("localhost") + if err != nil { + log.Fatalf("failed to find a free port to start gRPC REST server: %s", err) + } + ipcPath := InitgRPCServer(t, true, freePort) + var conn *grpc.ClientConn + conn, err = grpc.Dial(fmt.Sprintf("passthrough:///unix://%s", ipcPath), grpc.WithInsecure()) + if err != nil { + t.Fatalf("Connection to gRPC server failed with error %s", err) + } + defer conn.Close() + c := chimera.NewClientClient(conn) + + for _, receiveReq := range receiveReqs { + resp, err := c.Receive(context.Background(), &receiveReq) + if err != nil { + t.Fatalf("gRPC receive failed with %s", err) + } + response := chimera.ReceiveResponse{Payload: resp.Payload} + if !reflect.DeepEqual(response, expected) { + t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected) + } + } +} + func TestReceivedRaw(t *testing.T) { tm := TransactionManager{Enclave: &MockEnclave{}} @@ -153,24 +251,32 @@ func TestReceivedRaw(t *testing.T) { runRawHandlerTest(t, headers, payload, payload, receiveRaw, tm.receiveRaw) } +func TestNilKeyReceivedRaw(t *testing.T) { + tm := TransactionManager{Enclave: &MockEnclave{}} + + headers := make(http.Header) + headers[hKey] = []string{""} + headers[hTo] = []string{receiver} + + runFailingRawHandlerTest(t, headers, payload, payload, receiveRaw, tm.receiveRaw) +} + func TestPush(t *testing.T) { epl := api.EncryptedPayload{ - Sender: nacl.NewKey(), - CipherText: []byte(payload), - Nonce: nacl.NewNonce(), + Sender: nacl.NewKey(), + CipherText: []byte(payload), + Nonce: nacl.NewNonce(), RecipientBoxes: [][]byte{[]byte(payload)}, RecipientNonce: nacl.NewNonce(), } var recipients [][]byte encoded := api.EncodePayloadWithRecipients(epl, recipients) - req, err := http.NewRequest("POST", push, bytes.NewBuffer(encoded)) if err != nil { t.Fatal(err) } - rr := httptest.NewRecorder() tm := TransactionManager{Enclave: &MockEnclave{}} @@ -239,6 +345,32 @@ func runJsonHandlerTest( t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected) } } +func runFailingRawHandlerTest( + t *testing.T, + headers http.Header, + payload, expected []byte, + url string, + handlerFunc http.HandlerFunc) { + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload)) + if err != nil { + t.Fatal(err) + } + + for k, v := range headers { + req.Header.Set(k, v[0]) + } + + rr := httptest.NewRecorder() + + handler := http.HandlerFunc(handlerFunc) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status == http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } +} func runRawHandlerTest( t *testing.T, @@ -273,12 +405,11 @@ func runRawHandlerTest( } } - func TestResendIndividual(t *testing.T) { resendReq := api.ResendRequest{ - Type: "individual", - PublicKey: sender, - Key: encodedPayload, + Type: "individual", + PublicKey: sender, + Key: encodedPayload, } body := runResendTest(t, resendReq) @@ -291,8 +422,8 @@ func TestResendIndividual(t *testing.T) { func TestResendAll(t *testing.T) { resendReq := api.ResendRequest{ - Type: "all", - PublicKey: sender, + Type: "all", + PublicKey: sender, } body := runResendTest(t, resendReq) @@ -341,7 +472,7 @@ func TestPartyInfo(t *testing.T) { api.InitPartyInfo( "http://localhost:8000", []string{"http://localhost:8001"}, - http.DefaultClient), + http.DefaultClient, false), } for _, pi := range partyInfos { @@ -350,8 +481,11 @@ func TestPartyInfo(t *testing.T) { } func testRunPartyInfo(t *testing.T, pi api.PartyInfo) { - encoded := api.EncodePartyInfo(pi) - + encodedPartyInfo := api.EncodePartyInfo(pi) + encoded, err := json.Marshal(api.PartyInfoResponse{Payload: encodedPartyInfo}) + if err != nil { + t.Errorf("Marshalling failed %v", err) + } req, err := http.NewRequest("POST", push, bytes.NewBuffer(encoded)) if err != nil { t.Fatal(err) @@ -372,4 +506,57 @@ func testRunPartyInfo(t *testing.T, pi api.PartyInfo) { t.Errorf("handler returned unexpected body: got %v wanted %v\n", rr.Body.Bytes(), payload) } -} \ No newline at end of file +} + +func InitgRPCServer(t *testing.T, grpc bool, port int) string { + ipcPath, err := ioutil.TempDir("", "TestInitIpc") + tm, err := Init(&MockEnclave{}, "localhost", port, ipcPath, grpc, -1, false, "", "") + + if err != nil { + t.Errorf("Error starting server: %v\n", err) + } + runSimpleGetRequest(t, upCheck, upCheckResponse, tm.upcheck) + return ipcPath +} + +func TestInit(t *testing.T) { + dbPath, err := ioutil.TempDir("", "TestInit") + if err != nil { + t.Error(err) + } + db, err := storage.InitLevelDb(dbPath) + if err != nil { + t.Errorf("Error starting server: %v\n", err) + } + pubKeyFiles := []string{"key.pub"} + privKeyFiles := []string{"key"} + + for i, keyFile := range privKeyFiles { + privKeyFiles[i] = path.Join("../enclave/testdata", keyFile) + } + + for i, keyFile := range pubKeyFiles { + pubKeyFiles[i] = path.Join("../enclave/testdata", keyFile) + } + + key := []nacl.Key{nacl.NewKey()} + + pi := api.CreatePartyInfo( + "http://localhost:9000", + []string{"http://localhost:9001"}, + key, + http.DefaultClient) + + enc := enclave.Init(db, pubKeyFiles, privKeyFiles, pi, http.DefaultClient, false) + + ipcPath, err := ioutil.TempDir("", "TestInitIpc") + if err != nil { + t.Error(err) + } + certFile, keyFile := "../enclave/testdata/cert/server.crt", "../enclave/testdata/cert/server.key" + tm, err := Init(enc, "localhost", 9001, ipcPath, false, -1, true, certFile, keyFile) + if err != nil { + t.Errorf("Error starting server: %v\n", err) + } + runSimpleGetRequest(t, upCheck, upCheckResponse, tm.upcheck) +} diff --git a/storage/berkleydb.go b/storage/berkleydb.go index 5babb3d..d2ee2a7 100644 --- a/storage/berkleydb.go +++ b/storage/berkleydb.go @@ -3,7 +3,6 @@ package storage import ( "encoding/base64" "github.com/jsimonetti/berkeleydb" - "github.com/blk-io/crux/utils" ) type berkleyDb struct { @@ -48,7 +47,7 @@ func (db *berkleyDb) Read(key *[]byte) (*[]byte, error) { } var decoded []byte - decoded, err = utils.DecodeBase64(value) + decoded, err = base64.StdEncoding.DecodeString(value) return &decoded, err } @@ -85,4 +84,4 @@ func (db *berkleyDb) Delete(key *[]byte) error { func (db *berkleyDb) Close() error { return db.conn.Close() -} \ No newline at end of file +} diff --git a/storage/datastore.go b/storage/datastore.go index c46b0a1..b78f27b 100644 --- a/storage/datastore.go +++ b/storage/datastore.go @@ -1,5 +1,6 @@ package storage +// DataStore is an interface that facilitates operations with an underlying persistent data store. type DataStore interface { Write(key *[]byte, value *[]byte) error Read(key *[]byte) (*[]byte, error) diff --git a/storage/leveldb.go b/storage/leveldb.go index 84940ff..da89a2a 100644 --- a/storage/leveldb.go +++ b/storage/leveldb.go @@ -46,4 +46,4 @@ func (db *levelDb) Delete(key *[]byte) error { func (db *levelDb) Close() error { return db.conn.Close() -} \ No newline at end of file +} diff --git a/test.txt b/test.txt deleted file mode 100644 index 5b8d126..0000000 --- a/test.txt +++ /dev/null @@ -1,13 +0,0 @@ -curl -X GET http://localhost:8000/upcheck - - -curl -d '{"payload":"bWVzc2FnZSBwYXlsb2FkMQ==","from":"BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=","to":["QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="]}' -H "Content-Type: application/json" -X POST http://localhost:8000/send - -curl -d '{"key":"1ifl2vtckF+8SiqIwFfPkmbkngbanOZ4Pu886ehqRGMagWwJ0BviEymdekCLS/S66sGz7mWEKjwEMkSY49TMdQ==","to":"QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="}' -H "Content-Type: application/json" -X POST http://localhost:8000/receive - -leveldbutil dump ~/code/go/blk-io/tmp/crux.db/000001.log - - -// constellation-node --url=https://127.0.0.7:9007/ --port=9007 --workdir=qdata/c7 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/ >> qdata/logs/constellation7.log 2>&1 & - -// "/Users/Conor/code/go/blk-io/tmp/crux.db" diff --git a/test/client_test.go b/test/client_test.go new file mode 100644 index 0000000..b91ae0c --- /dev/null +++ b/test/client_test.go @@ -0,0 +1,82 @@ +package client + +import ( + "encoding/base64" + "github.com/blk-io/chimera-api/chimera" + "golang.org/x/net/context" + "google.golang.org/grpc" + "reflect" + "testing" +) + +const sender = "zSifTnkv5r4K67Dq304eVcM4FpxGfHLe1yTCBm0/7wg=" +const receiver = "I/EbshW61ykJ+qTivXPaKyQ5WmQDUFfMNGEBj2E2uUs=" + +var payload = []byte("payload") + +func TestIntegration(t *testing.T) { + var conn1 *grpc.ClientConn + var conn2 *grpc.ClientConn + // Initiate a connection with the first server + conn1, err := grpc.Dial(":9020", grpc.WithInsecure()) + if err != nil { + t.Fatalf("did not connect: %s", err) + } + defer conn1.Close() + c1 := chimera.NewClientClient(conn1) + // Initiate a connection with the second server + conn2, err = grpc.Dial(":9025", grpc.WithInsecure()) + if err != nil { + t.Fatalf("did not connect: %s", err) + } + defer conn2.Close() + c2 := chimera.NewClientClient(conn2) + + Upcheckresponse1, err := c1.Upcheck(context.Background(), &chimera.UpCheckResponse{Message: "foo"}) + if err != nil { + t.Fatalf("error when calling Upcheck: %s", err) + } + t.Logf("Response from server: %s", Upcheckresponse1.Message) + + Upcheckresponse2, err := c2.Upcheck(context.Background(), &chimera.UpCheckResponse{Message: "foo"}) + if err != nil { + t.Fatalf("error when calling Upcheck: %s", err) + } + t.Logf("Response from server: %s", Upcheckresponse2.Message) + + sendReqs := []chimera.SendRequest{ + { + Payload: []byte("payload"), + From: sender, + To: []string{receiver}, + }, + { + Payload: []byte("test"), + To: []string{}, + }, + { + Payload: []byte("blk-io crux"), + }, + } + + sendResponse := chimera.SendResponse{} + for _, sendReq := range sendReqs { + sendResp, err := c1.Send(context.Background(), &sendReq) + if err != nil { + t.Fatalf("gRPC send failed with %s", err) + } + sendResponse = chimera.SendResponse{Key: sendResp.Key} + t.Logf("The response for Send request is: %s", base64.StdEncoding.EncodeToString(sendResponse.Key)) + + recResp, err := c1.Receive(context.Background(), &chimera.ReceiveRequest{Key: sendResponse.Key, To: receiver}) + if err != nil { + t.Fatalf("gRPC receive failed with %s", err) + } + receiveResponse := chimera.ReceiveResponse{Payload: recResp.Payload} + if !reflect.DeepEqual(receiveResponse.Payload, sendReq.Payload) { + t.Fatalf("handler returned unexpected response: %v, expected: %v\n", receiveResponse.Payload, sendReq.Payload) + } else { + t.Logf("The payload return is %v", receiveResponse.Payload) + } + } +} diff --git a/test/test1/testdata/key b/test/test1/testdata/key new file mode 100644 index 0000000..fdb841b --- /dev/null +++ b/test/test1/testdata/key @@ -0,0 +1 @@ +{"data":{"bytes":"W1n0C+NfjcU/cUBXsP5FQ/frU+qpvKQ7Pi/Mu5Hf/Ic="},"type":"unlocked"} \ No newline at end of file diff --git a/test/test1/testdata/key.pub b/test/test1/testdata/key.pub new file mode 100644 index 0000000..e8d6224 --- /dev/null +++ b/test/test1/testdata/key.pub @@ -0,0 +1 @@ +zSifTnkv5r4K67Dq304eVcM4FpxGfHLe1yTCBm0/7wg= \ No newline at end of file diff --git a/test/test2/testdata/rcpt1 b/test/test2/testdata/rcpt1 new file mode 100644 index 0000000..6d0736c --- /dev/null +++ b/test/test2/testdata/rcpt1 @@ -0,0 +1 @@ +{"data":{"bytes":"9e0UrkhdfGY0kBYuk3Nv3g4FlYXjTHpXORWO2r1An/A="},"type":"unlocked"} \ No newline at end of file diff --git a/test/test2/testdata/rcpt1.pub b/test/test2/testdata/rcpt1.pub new file mode 100644 index 0000000..6599ad6 --- /dev/null +++ b/test/test2/testdata/rcpt1.pub @@ -0,0 +1 @@ +I/EbshW61ykJ+qTivXPaKyQ5WmQDUFfMNGEBj2E2uUs= \ No newline at end of file diff --git a/utils/base64.go b/utils/base64.go deleted file mode 100644 index 3c011ff..0000000 --- a/utils/base64.go +++ /dev/null @@ -1,9 +0,0 @@ -package utils - -import "encoding/base64" - -func DecodeBase64(value string) ([]byte, error) { - dest := make([]byte, base64.StdEncoding.DecodedLen(len(value))) - n, err := base64.StdEncoding.Decode(dest, []byte(value)) - return dest[:n], err -} \ No newline at end of file diff --git a/utils/file_test.go b/utils/file_test.go new file mode 100644 index 0000000..9abb25d --- /dev/null +++ b/utils/file_test.go @@ -0,0 +1,23 @@ +package utils + +import ( + "io/ioutil" + "testing" +) + +func TestCreateIpcSocket(t *testing.T) { + dbPath, err := ioutil.TempDir("", "TestCreateIpcSocket") + if err != nil { + t.Error(err) + } + + listener, err := CreateIpcSocket(dbPath) + + if err != nil { + t.Error(err) + } + + if listener == nil { + t.Errorf("Listener not initialised") + } +} diff --git a/utils/http.go b/utils/http.go index 990b1fc..efb5f64 100644 --- a/utils/http.go +++ b/utils/http.go @@ -2,6 +2,7 @@ package utils import "net/http" +// HttpClient is an interface for sending synchronous HTTP requests. type HttpClient interface { Do(req *http.Request) (*http.Response, error) } diff --git a/utils/key.go b/utils/key.go index 4a1dffa..7b8431f 100644 --- a/utils/key.go +++ b/utils/key.go @@ -1,9 +1,9 @@ package utils import ( - "github.com/kevinburke/nacl" - "fmt" "encoding/base64" + "fmt" + "github.com/kevinburke/nacl" ) func ToKey(src []byte) (nacl.Key, error) { diff --git a/utils/math.go b/utils/math.go index 2c57916..1b1841c 100644 --- a/utils/math.go +++ b/utils/math.go @@ -1,7 +1,5 @@ package utils - - func NextPowerOf2(v int) int { v-- v |= v >> 1 @@ -11,4 +9,4 @@ func NextPowerOf2(v int) int { v |= v >> 16 v++ return v -} \ No newline at end of file +} diff --git a/utils/math_test.go b/utils/math_test.go index bf04ae3..25f4fe4 100644 --- a/utils/math_test.go +++ b/utils/math_test.go @@ -5,17 +5,17 @@ import "testing" func TestNextPowerOf2(t *testing.T) { values := map[int]int{ - 0: 0, - 1: 1, - 2: 2, - 3: 4, - 4: 4, - 5: 8, - 7: 8, - 8: 8, - 9: 16, - 16: 16, - 17: 32, + 0: 0, + 1: 1, + 2: 2, + 3: 4, + 4: 4, + 5: 8, + 7: 8, + 8: 8, + 9: 16, + 16: 16, + 17: 32, 1023: 1024, 1024: 1024, 1025: 2048, diff --git a/utils/url_test.go b/utils/url_test.go index f1e92a2..bfa4d15 100644 --- a/utils/url_test.go +++ b/utils/url_test.go @@ -19,4 +19,4 @@ func runUrlTest(t *testing.T, baseUrl, path, expected string) { if url != expected { t.Errorf("Url created: %s, does not match expected: %s", url, expected) } -} \ No newline at end of file +}