├── docker
├── quorum-crux
│ ├── passwords.txt
│ ├── start.sh
│ ├── scripts
│ │ ├── test_transaction.sh
│ │ └── simpleContract.js
│ ├── istanbul-init.sh
│ ├── bootstrap.sh
│ ├── crux-start.sh
│ ├── istanbul-start.sh
│ ├── Dockerfile
│ ├── istanbul-genesis.json
│ ├── docker-compose.yaml
│ └── docker-compose-local.yaml
└── crux
│ ├── start.sh
│ ├── Dockerfile
│ └── docker-compose.yml
├── enclave
├── testdata
│ ├── key.pub
│ ├── rcpt1.pub
│ ├── rcpt2.pub
│ ├── key
│ ├── rcpt1
│ └── cert
│ │ ├── server.csr
│ │ ├── server.crt
│ │ └── server.key
├── enclave_test.go
└── enclave.go
├── test
├── test1
│ └── testdata
│ │ ├── key.pub
│ │ └── key
├── test2
│ └── testdata
│ │ ├── rcpt1.pub
│ │ └── rcpt1
└── client_test.go
├── docs
├── quorum-architecture.png
├── read-tx.mermaid
├── new-tx.mermaid
├── quorum-architecture.xml
├── read-tx.svg
└── new-tx.svg
├── .gitignore
├── utils
├── math.go
├── hash.go
├── http.go
├── url.go
├── file_test.go
├── math_test.go
├── key.go
├── file.go
└── url_test.go
├── storage
├── datastore.go
├── leveldb.go
└── berkleydb.go
├── api
├── internal_test.go
├── client.go
├── encoding_test.go
├── encoding.go
└── internal.go
├── CHANGELOG.md
├── .travis.yml
├── .gitlab-ci.yml
├── Gopkg.toml
├── config
├── config_test.go
├── config.go
└── config_testdata.conf
├── crux.go
├── server
├── proto_server.go
├── server_handler.go
├── server.go
└── server_test.go
├── Makefile
├── README.md
├── Gopkg.lock
└── LICENSE
/docker/quorum-crux/passwords.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/enclave/testdata/key.pub:
--------------------------------------------------------------------------------
1 | zSifTnkv5r4K67Dq304eVcM4FpxGfHLe1yTCBm0/7wg=
--------------------------------------------------------------------------------
/enclave/testdata/rcpt1.pub:
--------------------------------------------------------------------------------
1 | I/EbshW61ykJ+qTivXPaKyQ5WmQDUFfMNGEBj2E2uUs=
--------------------------------------------------------------------------------
/enclave/testdata/rcpt2.pub:
--------------------------------------------------------------------------------
1 | evN1e8V503XDjXOovu7YeD3eD4+/0GiSvbxQPJutQiw=
--------------------------------------------------------------------------------
/test/test1/testdata/key.pub:
--------------------------------------------------------------------------------
1 | zSifTnkv5r4K67Dq304eVcM4FpxGfHLe1yTCBm0/7wg=
--------------------------------------------------------------------------------
/test/test2/testdata/rcpt1.pub:
--------------------------------------------------------------------------------
1 | I/EbshW61ykJ+qTivXPaKyQ5WmQDUFfMNGEBj2E2uUs=
--------------------------------------------------------------------------------
/enclave/testdata/key:
--------------------------------------------------------------------------------
1 | {"data":{"bytes":"W1n0C+NfjcU/cUBXsP5FQ/frU+qpvKQ7Pi/Mu5Hf/Ic="},"type":"unlocked"}
--------------------------------------------------------------------------------
/docs/quorum-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web3labs/crux/HEAD/docs/quorum-architecture.png
--------------------------------------------------------------------------------
/enclave/testdata/rcpt1:
--------------------------------------------------------------------------------
1 | {"data":{"bytes":"9e0UrkhdfGY0kBYuk3Nv3g4FlYXjTHpXORWO2r1An/A="},"type":"unlocked"}
--------------------------------------------------------------------------------
/test/test1/testdata/key:
--------------------------------------------------------------------------------
1 | {"data":{"bytes":"W1n0C+NfjcU/cUBXsP5FQ/frU+qpvKQ7Pi/Mu5Hf/Ic="},"type":"unlocked"}
--------------------------------------------------------------------------------
/test/test2/testdata/rcpt1:
--------------------------------------------------------------------------------
1 | {"data":{"bytes":"9e0UrkhdfGY0kBYuk3Nv3g4FlYXjTHpXORWO2r1An/A="},"type":"unlocked"}
--------------------------------------------------------------------------------
/docker/quorum-crux/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ./istanbul-init.sh
4 | ./istanbul-start.sh
5 |
6 | tail -f /dev/null
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ project files
2 | *.iml
3 | .idea/
4 |
5 | # OS X
6 | .DS_Store
7 |
8 | /.GOPATH
9 | /bin
10 | /vendor
11 | /test/test1
12 | /test/test2
13 |
14 |
--------------------------------------------------------------------------------
/utils/math.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func NextPowerOf2(v int) int {
4 | v--
5 | v |= v >> 1
6 | v |= v >> 2
7 | v |= v >> 4
8 | v |= v >> 8
9 | v |= v >> 16
10 | v++
11 | return v
12 | }
13 |
--------------------------------------------------------------------------------
/docker/quorum-crux/scripts/test_transaction.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Sending private transaction"
3 | PRIVATE_CONFIG=qdata/c/tm.ipc geth --exec "loadScript(\"simpleContract.js\")" attach ipc:qdata/dd/geth.ipc
4 |
--------------------------------------------------------------------------------
/utils/hash.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "golang.org/x/crypto/sha3"
4 |
5 | func Sha3Hash(payload []byte) []byte {
6 | sha3Hash := sha3.New512()
7 | sha3Hash.Write(payload)
8 | return sha3Hash.Sum(nil)
9 | }
10 |
--------------------------------------------------------------------------------
/utils/http.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "net/http"
4 |
5 | // HttpClient is an interface for sending synchronous HTTP requests.
6 | type HttpClient interface {
7 | Do(req *http.Request) (*http.Response, error)
8 | }
9 |
--------------------------------------------------------------------------------
/docker/crux/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo $CRUX_PUB >> key.pub
4 | echo $CRUX_PRIV >> key.priv
5 | 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"
6 | $CMD >> "crux.log" 2>&1
--------------------------------------------------------------------------------
/docs/read-tx.mermaid:
--------------------------------------------------------------------------------
1 | sequenceDiagram
2 | participant Client
3 | participant Quorum
4 | participant Crux
5 |
6 | Client->>Quorum: getQuorumPayload(transactionHash, to)
7 | Quorum->>Crux: receive(transactionHash, to)
8 | Crux-->>Quorum: receiveResponse(transaction)
9 | Quorum->>Client: transaction
10 |
--------------------------------------------------------------------------------
/docs/new-tx.mermaid:
--------------------------------------------------------------------------------
1 | sequenceDiagram
2 | participant Client
3 | participant Quorum
4 | participant Crux
5 | participant Remote Crux
6 |
7 | Client->>Quorum: send(transaction, privateFor)
8 | Quorum->>Crux: send(transaction, privateFor)
9 | Crux->>Remote Crux: push(encryptedPayload)
10 | Crux-->>Quorum: sendResponse(transactionHash)
11 |
--------------------------------------------------------------------------------
/storage/datastore.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | // DataStore is an interface that facilitates operations with an underlying persistent data store.
4 | type DataStore interface {
5 | Write(key *[]byte, value *[]byte) error
6 | Read(key *[]byte) (*[]byte, error)
7 | ReadAll(f func(key, value *[]byte)) error
8 | Delete(key *[]byte) error
9 | Close() error
10 | }
11 |
--------------------------------------------------------------------------------
/utils/url.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "net/url"
4 |
5 | func BuildUrl(rawUrl, rawPath string) (string, error) {
6 | baseUrl, err := url.Parse(rawUrl)
7 | if err != nil {
8 | return "", err
9 | }
10 |
11 | path, err := url.Parse(rawPath)
12 |
13 | if err != nil {
14 | return "", err
15 | }
16 |
17 | return baseUrl.ResolveReference(path).String(), nil
18 | }
19 |
--------------------------------------------------------------------------------
/utils/file_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io/ioutil"
5 | "testing"
6 | )
7 |
8 | func TestCreateIpcSocket(t *testing.T) {
9 | dbPath, err := ioutil.TempDir("", "TestCreateIpcSocket")
10 | if err != nil {
11 | t.Error(err)
12 | }
13 |
14 | listener, err := CreateIpcSocket(dbPath)
15 |
16 | if err != nil {
17 | t.Error(err)
18 | }
19 |
20 | if listener == nil {
21 | t.Errorf("Listener not initialised")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/docker/quorum-crux/istanbul-init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -u
3 | set -e
4 |
5 | echo "[*] Cleaning up temporary data directories"
6 | rm -rf qdata
7 | mkdir -p qdata/logs
8 |
9 | echo "[*] Configuring node"
10 | mkdir -p qdata/dd/{keystore,geth}
11 | echo $PERMISSIONED_NODES >> qdata/dd/static-nodes.json
12 | echo $PERMISSIONED_NODES >> qdata/dd/permissioned-nodes.json
13 | echo $GETH_KEY >> qdata/dd/keystore/key
14 | geth --datadir qdata/dd init istanbul-genesis.json
15 |
--------------------------------------------------------------------------------
/docker/quorum-crux/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eu -o pipefail
3 |
4 | # make/install quorum
5 | git clone https://github.com/ConsenSys/quorum.git
6 | pushd quorum >/dev/null
7 | git checkout tags/v2.0.3-grpc
8 | make all
9 | cp build/bin/geth /usr/local/bin
10 | cp build/bin/bootnode /usr/local/bin
11 | rm -r build
12 | popd >/dev/null
13 |
14 | # make/install crux
15 | git clone https://github.com/blk-io/crux.git
16 | cd crux
17 | git checkout tags/v1.0.3
18 | make setup && make
19 | cp bin/crux /usr/local/bin
20 | rm -r bin
21 |
--------------------------------------------------------------------------------
/utils/math_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "testing"
4 |
5 | func TestNextPowerOf2(t *testing.T) {
6 |
7 | values := map[int]int{
8 | 0: 0,
9 | 1: 1,
10 | 2: 2,
11 | 3: 4,
12 | 4: 4,
13 | 5: 8,
14 | 7: 8,
15 | 8: 8,
16 | 9: 16,
17 | 16: 16,
18 | 17: 32,
19 | 1023: 1024,
20 | 1024: 1024,
21 | 1025: 2048,
22 | }
23 |
24 | for value, expected := range values {
25 | if NextPowerOf2(value) != expected {
26 | t.Errorf("Next power of 2 for %d, does not equal %d", value, expected)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/utils/key.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "github.com/kevinburke/nacl"
7 | )
8 |
9 | func ToKey(src []byte) (nacl.Key, error) {
10 | if len(src) != nacl.KeySize {
11 | return nil, fmt.Errorf("nacl: incorrect key length: %d", len(src))
12 | }
13 | key := new([nacl.KeySize]byte)
14 | copy(key[:], src)
15 | return key, nil
16 | }
17 |
18 | func LoadBase64Key(key string) (nacl.Key, error) {
19 | src, err := base64.StdEncoding.DecodeString(key)
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | return ToKey(src)
25 | }
26 |
--------------------------------------------------------------------------------
/utils/file.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "net"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | func CreateIpcSocket(path string) (net.Listener, error) {
10 | err := CreateDirForFile(path)
11 | if err != nil {
12 | return nil, err
13 | }
14 | os.Remove(path)
15 |
16 | var listener net.Listener
17 | listener, err = net.Listen("unix", path)
18 | if err != nil {
19 | return nil, err
20 | }
21 | os.Chmod(path, 0600)
22 |
23 | return listener, nil
24 | }
25 |
26 | func CreateDirForFile(path string) error {
27 | return os.MkdirAll(filepath.Dir(path), os.FileMode(0755))
28 | }
29 |
--------------------------------------------------------------------------------
/docker/quorum-crux/crux-start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -u
3 | set -e
4 |
5 | DDIR="qdata/c"
6 | mkdir -p $DDIR
7 | mkdir -p qdata/logs
8 | echo $CRUX_PUB >> "$DDIR/tm.pub"
9 | echo $CRUX_PRIV >> "$DDIR/tm.key"
10 | rm -f "$DDIR/tm.ipc"
11 | 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"
12 | $CMD >> "qdata/logs/crux.log" 2>&1 &
13 |
14 | DOWN=true
15 | while $DOWN; do
16 | sleep 0.1
17 | DOWN=false
18 | if [ ! -S "qdata/c/tm.ipc" ]; then
19 | DOWN=true
20 | fi
21 | done
22 |
--------------------------------------------------------------------------------
/api/internal_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/kevinburke/nacl"
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | func TestRegisterPublicKeys(t *testing.T) {
10 | key := []nacl.Key{nacl.NewKey()}
11 |
12 | pi := CreatePartyInfo(
13 | "http://localhost:9000",
14 | []string{"http://localhost:9001"},
15 | key,
16 | http.DefaultClient)
17 |
18 | expKey := []nacl.Key{nacl.NewKey()}
19 | expUrl := "http://localhost:9000"
20 |
21 | pi.RegisterPublicKeys(expKey)
22 |
23 | url, ok := pi.GetRecipient(expKey[0])
24 | if !ok || url != expUrl {
25 | t.Errorf("Url is %s whereas %s is expected", url, expUrl)
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.3 - 2018-10-17
2 | ### Added
3 | - Network interface paramater to configuration
4 | ### Changed
5 | - Fix formatting of entire project using `go fmt`
6 | - Fix "apk WARNING Ignoring APKINDEX"
7 | - Fix sending payload to multiple recipients
8 |
9 | ## 1.0.2 - 2018-09-06
10 | ### Added
11 | - Delete and Resend API from Chimera included.
12 |
13 | ## 1.0.1 - 2018-08-22
14 | ### Changed
15 | - Fix issue with config file load
16 |
17 | ## 1.0.0 - 2018-07-05
18 | ### Added
19 | - Crux, a secure enclave for Quorum written in Golang
20 | - Protobuf and gRPC support.
21 | - TLS support
22 | - Docker images
23 |
--------------------------------------------------------------------------------
/docker/quorum-crux/istanbul-start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -u
3 | set -e
4 |
5 | mkdir -p qdata/logs
6 | echo "[*] Starting Crux nodes"
7 | ./crux-start.sh
8 |
9 | echo "[*] Starting Ethereum nodes"
10 | set -v
11 | ARGS="--syncmode full --mine --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul "
12 | ARGS=$ARGS"--txpool.globalslots 20000 --txpool.globalqueue 20000 --istanbul.blockperiod 1 "
13 | # ARGS=$ARGS"--cache 4096 --trie-cache-gens 1000 "
14 | 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 &
15 | set +v
16 |
17 |
18 |
--------------------------------------------------------------------------------
/utils/url_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "testing"
4 |
5 | func TestBuildUrl(t *testing.T) {
6 | runUrlTest(t, "http://localhost:9001/", "/endpoint", "http://localhost:9001/endpoint")
7 | runUrlTest(t, "http://localhost:9001", "/endpoint", "http://localhost:9001/endpoint")
8 | runUrlTest(t, "http://localhost:9001", "endpoint", "http://localhost:9001/endpoint")
9 | runUrlTest(t, "http://localhost:9001//", "/endpoint", "http://localhost:9001/endpoint")
10 | }
11 |
12 | func runUrlTest(t *testing.T, baseUrl, path, expected string) {
13 | url, err := BuildUrl(baseUrl, path)
14 |
15 | if err != nil {
16 | t.Error(err)
17 | }
18 |
19 | if url != expected {
20 | t.Errorf("Url created: %s, does not match expected: %s", url, expected)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/docker/crux/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.8
2 |
3 | RUN apk update --no-cache && \
4 | # Update and then install dependencies
5 | apk add unzip db zlib wrk wget libsodium-dev go bash libpthread-stubs db-dev && \
6 | apk -X http://dl-cdn.alpinelinux.org/alpine/edge/testing add leveldb && \
7 | apk add build-base cmake boost-dev git
8 |
9 | ENV CRUX_PUB=""
10 | ENV CRUX_PRIV=""
11 | ENV OWN_URL=""
12 | ENV OTHER_NODES=""
13 | ENV PORT=""
14 |
15 | RUN git clone https://github.com/blk-io/crux.git
16 |
17 | WORKDIR /crux
18 |
19 | RUN make setup && \
20 | make build && \
21 | apk del sed make git cmake build-base gcc g++ musl-dev curl-dev boost-dev
22 | # fails https://github.com/golang/go/issues/14481
23 | # RUN make test
24 |
25 | EXPOSE 9000
26 |
27 | COPY start.sh start.sh
28 | RUN chmod +x start.sh
29 |
30 | ENTRYPOINT ["./start.sh"]
--------------------------------------------------------------------------------
/docker/crux/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.4"
2 | services:
3 | node1:
4 | image: blk.io/quorum/crux
5 | build:
6 | context: .
7 | container_name: crux1
8 | ports:
9 | - 9001:9000
10 | environment:
11 | - CRUX_PUB=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=
12 | - CRUX_PRIV={"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"}
13 | - OTHER_NODES=http://node2:9000/
14 | - OWN_URL=node1
15 | - PORT=9000
16 |
17 | node2:
18 | image: blk.io/quorum/crux
19 | build:
20 | context: .
21 | container_name: crux2
22 | ports:
23 | - 9002:9000
24 | environment:
25 | - CRUX_PUB=QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=
26 | - CRUX_PRIV={"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"}
27 | - OTHER_NODES=http://node1:9000/
28 | - OWN_URL=node2
29 | - PORT=9000
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - "1.10.x"
5 | - tip
6 |
7 | env:
8 | REPO_NAME: github.com/blk-io/crux
9 |
10 | before_install:
11 | - 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.
12 | - mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
13 | - ln -svf $TRAVIS_BUILD_DIR $GOPATH/src/$REPO_NAME
14 | - cd $GOPATH/src/$REPO_NAME
15 | - curl -OL https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip
16 | - unzip protoc-3.5.1-linux-x86_64.zip -d protoc3
17 | - sudo mv protoc3/bin/* /usr/local/bin/
18 | - sudo mv protoc3/include/* /usr/local/include/
19 | - rm -r protoc3
20 | - rm protoc-3.5.1-linux-x86_64.zip
21 | - go get -u github.com/golang/protobuf/protoc-gen-go
22 | - go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
23 | - make setup
24 |
25 | script:
26 | - make build
27 | - make test
28 | - make cover
29 |
30 | after_success:
31 | - bash <(curl -s https://codecov.io/bash)
32 |
--------------------------------------------------------------------------------
/enclave/testdata/cert/server.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIICsTCCAZkCAQAwVDELMAkGA1UEBhMCVUsxDzANBgNVBAgMBkxvbmRvbjEPMA0G
3 | A1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZibGstaW8xEjAQBgNVBAMMCTEyNy4wLjAu
4 | MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALSS/JHKnE9f9pU7jw3T
5 | Zint6aaj7jSEzV5BArjBs0GzDk/R+55AvE+428iFTywiAdeUBO3xxxEzCZ8OOLkf
6 | fJ95Vza0giWcR3/+F9na+1kCOuc2H/fiIvB8DMzGQxjukVLdIaCjl/F5Uyn7gKYK
7 | b7Trcryf/HkaaT3eA4TahC1dOhhq4q/WmkaWariM9QlFyJDfvYiL1XkmOuiiC6vD
8 | +yvd71dBtugDpIcNgmAZ+rVn4ZoC3Tc16OzeqrvxquF1YO0QakGqiGskZA6NTm+V
9 | L/GcubnFweKXkrvisTcqUleoGQM9TibbVQf/A0DamAVrauRs6rxva5JqW7Gh4o5d
10 | BUcCAwEAAaAYMBYGCSqGSIb3DQEJBzEJDAdibGtjcnV4MA0GCSqGSIb3DQEBCwUA
11 | A4IBAQBYRPFuhVoNhYBVoO4qaNet7W4JAZ2YkpTdE/SdECyBAt4lX4OPV+lFYrUD
12 | AjPf98IjwVwNijz6DboqEx+1rUKzLEV/Jv8k0O8noJDR3fRkFU3cyIDcW/N9Wwcc
13 | p3qHwRuSvv+QmDoikJeCR8iN61jeo6WG2KKB4GUETK35SMDS31ExkWHNa/WIFzrV
14 | MTB/Vy2yH0xgxhDRa8sEbsGrvf/XnhKKGNbffmTp46dBItBkDKeiHh2FGI6F8CBv
15 | bkhB0/7fC8Hi4aoBMSdT88L8Y7+mVMNfybmCuSr1foK+gMSS7UweLn2oPEMohFZW
16 | 4mkS8TD1bUkavlCrhVuExCSLVXLu
17 | -----END CERTIFICATE REQUEST-----
18 |
--------------------------------------------------------------------------------
/storage/leveldb.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "github.com/syndtr/goleveldb/leveldb"
5 | )
6 |
7 | type levelDb struct {
8 | dbPath string
9 | conn *leveldb.DB
10 | }
11 |
12 | func InitLevelDb(dbPath string) (*levelDb, error) {
13 | db := new(levelDb)
14 | db.dbPath = dbPath
15 | var err error
16 | db.conn, err = leveldb.OpenFile(dbPath, nil)
17 | return db, err
18 | }
19 |
20 | func (db *levelDb) Write(key *[]byte, value *[]byte) error {
21 | return db.conn.Put(*key, *value, nil)
22 | }
23 |
24 | func (db *levelDb) Read(key *[]byte) (*[]byte, error) {
25 | value, err := db.conn.Get(*key, nil)
26 | if err == nil {
27 | return &value, err
28 | } else {
29 | return nil, err
30 | }
31 | }
32 |
33 | func (db *levelDb) ReadAll(f func(key, value *[]byte)) error {
34 | iter := db.conn.NewIterator(nil, nil)
35 | for iter.Next() {
36 | key, value := iter.Key(), iter.Value()
37 | f(&key, &value)
38 | }
39 | iter.Release()
40 | return iter.Error()
41 | }
42 |
43 | func (db *levelDb) Delete(key *[]byte) error {
44 | return db.conn.Delete(*key, nil)
45 | }
46 |
47 | func (db *levelDb) Close() error {
48 | return db.conn.Close()
49 | }
50 |
--------------------------------------------------------------------------------
/enclave/testdata/cert/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDJDCCAgwCCQDmw4WXCHzRUTANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJV
3 | SzEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25kb24xDzANBgNVBAoMBmJs
4 | ay1pbzESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTE4MDYxNDE0Mzg1NFoXDTI4MDYx
5 | MTE0Mzg1NFowVDELMAkGA1UEBhMCVUsxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UE
6 | BwwGTG9uZG9uMQ8wDQYDVQQKDAZibGstaW8xEjAQBgNVBAMMCTEyNy4wLjAuMTCC
7 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALSS/JHKnE9f9pU7jw3TZint
8 | 6aaj7jSEzV5BArjBs0GzDk/R+55AvE+428iFTywiAdeUBO3xxxEzCZ8OOLkffJ95
9 | Vza0giWcR3/+F9na+1kCOuc2H/fiIvB8DMzGQxjukVLdIaCjl/F5Uyn7gKYKb7Tr
10 | cryf/HkaaT3eA4TahC1dOhhq4q/WmkaWariM9QlFyJDfvYiL1XkmOuiiC6vD+yvd
11 | 71dBtugDpIcNgmAZ+rVn4ZoC3Tc16OzeqrvxquF1YO0QakGqiGskZA6NTm+VL/Gc
12 | ubnFweKXkrvisTcqUleoGQM9TibbVQf/A0DamAVrauRs6rxva5JqW7Gh4o5dBUcC
13 | AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYHeXraxdqSm5PTYrWqVks+5ctTv/tO4w
14 | cEtleZHy86qnmtu8gqHH+tAKVjn/JNGvYNX/M0oMPjxn0LffGCH6MhC9072RDA10
15 | XC+yUQx2tm5Q8RRVjb/S+6GczAqXVE6Qc59zgTaFtE8xc/J/wW6XPhJ99Jly2oba
16 | y/6UMvPfCLtf7pA99V4lXat0oRYtVbFkyxIGVl0Gx2COJx65UUtUs+Grf41CQCYr
17 | VXSUTl6wTNj6llAMDkgyFobmfwWzr0DWJJSDycHuC5oxdIe6nhLRq1EgU5DHwyGD
18 | Mz9tLPJHGpJPX8ml4U/XuiYHNDt5hJYZ0veJ6fFM3iSBMCfBIh4Npg==
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/docker/quorum-crux/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.8
2 |
3 | RUN apk update --no-cache && \
4 | # Update and then install dependencies
5 | apk add unzip db zlib wrk wget libsodium-dev go bash libpthread-stubs db-dev && \
6 | apk -X http://dl-cdn.alpinelinux.org/alpine/edge/testing add leveldb && \
7 | apk add build-base cmake boost-dev git
8 |
9 | ENV PORT=""
10 | ENV NODE_KEY=""
11 | ENV CRUX_PUB=""
12 | ENV GETH_KEY=""
13 | ENV OWN_URL=""
14 | ENV CRUX_PRIV=""
15 | ENV OTHER_NODES=""
16 | ENV GETH_RPC_PORT=""
17 | ENV GETH_PORT=""
18 |
19 | WORKDIR /quorum
20 |
21 | COPY bootstrap.sh bootstrap.sh
22 | COPY istanbul-genesis.json istanbul-genesis.json
23 | COPY passwords.txt passwords.txt
24 | COPY istanbul-init.sh istanbul-init.sh
25 | COPY crux-start.sh crux-start.sh
26 | COPY istanbul-start.sh istanbul-start.sh
27 | COPY start.sh start.sh
28 | COPY scripts/simpleContract.js simpleContract.js
29 | COPY scripts/test_transaction.sh test_transaction.sh
30 |
31 | RUN chmod +x start.sh crux-start.sh istanbul-start.sh istanbul-init.sh && \
32 | chmod +x test_transaction.sh && \
33 | chmod +x bootstrap.sh && \
34 | ./bootstrap.sh && \
35 | apk del sed make git cmake build-base gcc g++ musl-dev curl-dev boost-dev
36 |
37 | EXPOSE 9000 21000 22000
38 |
39 | # Entrypoint for container
40 | ENTRYPOINT ["./start.sh"]
41 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: golang:latest
2 |
3 | variables:
4 | REPO_NAME: github.com/blk-io/crux
5 |
6 | # The problem is that to be able to use go get, one needs to put
7 | # the repository in the $GOPATH. So for example if your gitlab domain
8 | # is gitlab.com, and that your repository is namespace/project, and
9 | # the default GOPATH being /go, then you'd need to have your
10 | # repository in /go/src/gitlab.com/namespace/project
11 | # Thus, making a symbolic link corrects this.
12 | before_script:
13 | - apt-get update -qq && apt-get install -y -qq libdb-dev libpthread-stubs0-dev # This is hopefully temporary until we completely remove BerkeleyDB.
14 | - mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
15 | - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME
16 | - cd $GOPATH/src/$REPO_NAME
17 | - go get ./...
18 | - make setup
19 |
20 | stages:
21 | - build
22 | - test
23 |
24 | format:
25 | stage: test
26 | script:
27 | - go fmt $(go list ./... | grep -v /vendor/)
28 | - go vet $(go list ./... | grep -v /vendor/)
29 | - make test
30 |
31 | compile:
32 | stage: build
33 | script:
34 | - make build
35 | artifacts:
36 | paths:
37 | - crux
38 |
39 | code_coverage:
40 | stage: test
41 | script:
42 | - rm -f $GOPATH/cover/*.out $GOPATH/cover/all.merged
43 | - mkdir -p $GOPATH/cover
44 | - make cover
45 |
46 |
--------------------------------------------------------------------------------
/Gopkg.toml:
--------------------------------------------------------------------------------
1 | # Gopkg.toml example
2 | #
3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
4 | # for detailed Gopkg.toml documentation.
5 | #
6 | # required = ["github.com/user/thing/cmd/thing"]
7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
8 | #
9 | # [[constraint]]
10 | # name = "github.com/user/project"
11 | # version = "1.0.0"
12 | #
13 | # [[constraint]]
14 | # name = "github.com/user/project2"
15 | # branch = "dev"
16 | # source = "github.com/myfork/project2"
17 | #
18 | # [[override]]
19 | # name = "github.com/x/y"
20 | # version = "2.4.0"
21 | #
22 | # [prune]
23 | # non-go = false
24 | # go-tests = true
25 | # unused-packages = true
26 |
27 |
28 | [[constraint]]
29 | branch = "master"
30 | name = "github.com/jsimonetti/berkeleydb"
31 |
32 | [[constraint]]
33 | name = "github.com/kevinburke/nacl"
34 | version = "0.5.0"
35 |
36 | [[constraint]]
37 | name = "github.com/sirupsen/logrus"
38 | version = "1.0.5"
39 |
40 | [[constraint]]
41 | name = "github.com/spf13/pflag"
42 | version = "1.0.0"
43 |
44 | [[constraint]]
45 | name = "github.com/spf13/viper"
46 | version = "1.0.2"
47 |
48 | [[constraint]]
49 | branch = "master"
50 | name = "github.com/syndtr/goleveldb"
51 |
52 | [[constraint]]
53 | branch = "master"
54 | name = "golang.org/x/crypto"
55 |
56 | [prune]
57 | go-tests = true
58 |
59 | [[constraint]]
60 | name = "github.com/grpc-ecosystem/grpc-gateway"
61 | version = "1.4.0"
62 |
63 | [[constraint]]
64 | name = "github.com/blk-io/chimera-api"
65 | revision = "ebd4db90873296427420c2fe2acec18c127b401d"
66 |
--------------------------------------------------------------------------------
/enclave/testdata/cert/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEAtJL8kcqcT1/2lTuPDdNmKe3ppqPuNITNXkECuMGzQbMOT9H7
3 | nkC8T7jbyIVPLCIB15QE7fHHETMJnw44uR98n3lXNrSCJZxHf/4X2dr7WQI65zYf
4 | 9+Ii8HwMzMZDGO6RUt0hoKOX8XlTKfuApgpvtOtyvJ/8eRppPd4DhNqELV06GGri
5 | r9aaRpZquIz1CUXIkN+9iIvVeSY66KILq8P7K93vV0G26AOkhw2CYBn6tWfhmgLd
6 | NzXo7N6qu/Gq4XVg7RBqQaqIayRkDo1Ob5Uv8Zy5ucXB4peSu+KxNypSV6gZAz1O
7 | JttVB/8DQNqYBWtq5GzqvG9rkmpbsaHijl0FRwIDAQABAoIBAFx/iteXxRg33RU2
8 | zCHV71h9IZoWEThf6t9kR+OifZOjCCrFMrIvEQd5d0QxXYwK44ytqxTELCfYUA5k
9 | 95OE2I7MVmuUbbKcalfbLhaPwP8oUEoOBLQy55juwpPG36oO0uxyj/48ruGoN3yi
10 | 85SadfxkO4L6JRdX+x8Q8haE5P5jDUZt/lol1neQR8moEpKwy+KhAEPBe3c+MlXl
11 | wHWYk/j8GHnfgcDCLITpu7dHGUDye7l0dbM4EjbU9R3vcsTfT18eekUqm6MER0Gd
12 | wA4bcrAhlNQjNyLocmSMgzD3NjeXzbbA4NXO32X6OmH+I+sGPEJHcZU0Kew0Depe
13 | yhkmXKkCgYEA5pRIhouqF3RzbGf+yMMdo9bQc7U4iUhTcZ9Vm/z/Nxs0d1pCHy5L
14 | n3VAgOSsIb8AP/DmhpRGyBAGodKnkpXrvMG03y7Pf2KjOxh/A2thTDeB4dcelEvu
15 | Q9Uj1s/BctyVzelrL0DZjBhwxBuT56lqq56E8Yb/jn7yZZ8g7A7vetMCgYEAyHtk
16 | qKfh3d4oF5/mro9KmWsmjB/jfSwXJzu9UsdmLFlRhsRZBXLYxwLxtAFzcM+y+ac3
17 | 18dowvBD2p3tvEwKVF0ncxn0vDNDF8n+sYlVsBrLuFccfw1bgl3jmmAhuXX6NZO8
18 | HcO/S6PjrnSKYd4WXRjHqiv5ufMZFQ1OKjz1mz0CgYEAmOi7E+ao3LcQGFL65p5m
19 | GJHLWQBTxs6c75uvhSuJAD1dVM0ZTl5ALjXumcuLzzE/9CdIaPUJ34CpNUVidVZQ
20 | p7N5xAvh9OMvxm/fQyBBvO6OhntHPyb/kiJViw3phsd73Lqvpv2Fh19p4NM9CYMT
21 | R05vcVCKRzAuhW+6wHDDJZsCgYEAxptXEK2f2Efot960jGFvqaS4v0AoASzYkwlf
22 | eM4Irg6d8UA6YGdx0VVdVNHLJwrbZu79J0powhV7YuvpRAygfwr5tdEU3gx6fuqg
23 | 4ggHVzp0bt39YPA+o1uXyqtJPY1eng0I4wO0Up69Q2o4XNPCm9+cjTybXFczleNk
24 | d/uD5JECgYBcwAPEFNU2+NcU+mYWFHIvNK4hVLD/Bfr6EmkJcOLYE+BZPdaWaPGv
25 | ZE9ZgLpf6bB9cmQGUifWdSHDXwQH7kpYKS5pJdGsPbuFys5HU4xCbCEErsRnPKlE
26 | K/hcgeDuKNJ6/nV1ypUsOXpO6ukhLnTvj1R9MQVipprTaQbxRBxx8g==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/docker/quorum-crux/scripts/simpleContract.js:
--------------------------------------------------------------------------------
1 | a = eth.accounts[0]
2 | web3.eth.defaultAccount = a;
3 |
4 | // abi and bytecode generated from simplestorage.sol:
5 | // > solcjs --bin --abi simplestorage.sol
6 | 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"}];
7 |
8 | var bytecode = "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029";
9 |
10 | var simpleContract = web3.eth.contract(abi);
11 | var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="]}, function(e, contract) {
12 | if (e) {
13 | console.log("err creating contract", e);
14 | } else {
15 | if (!contract.address) {
16 | console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
17 | } else {
18 | console.log("Contract mined! Address: " + contract.address);
19 | console.log(contract);
20 | }
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/storage/berkleydb.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "encoding/base64"
5 | "github.com/jsimonetti/berkeleydb"
6 | )
7 |
8 | type berkleyDb struct {
9 | dbPath string
10 | conn *berkeleydb.Db
11 | }
12 |
13 | func InitBerkeleyDb(dbPath string) (*berkleyDb, error) {
14 | bdb := &berkleyDb{dbPath: dbPath}
15 |
16 | db, err := berkeleydb.NewDB()
17 | if err != nil {
18 | return nil, err
19 | }
20 |
21 | err = db.Open(
22 | dbPath, berkeleydb.DbHash, berkeleydb.DbCreate)
23 |
24 | return bdb, err
25 | }
26 |
27 | func (db *berkleyDb) Write(key *[]byte, value *[]byte) error {
28 |
29 | b64Key := base64.StdEncoding.EncodeToString(*key)
30 | b64Value := base64.StdEncoding.EncodeToString(*value)
31 |
32 | err := db.conn.Put(b64Key, b64Value)
33 | if err != nil {
34 | return err
35 | } else {
36 | return nil
37 | }
38 | }
39 |
40 | func (db *berkleyDb) Read(key *[]byte) (*[]byte, error) {
41 |
42 | b64Key := base64.StdEncoding.EncodeToString(*key)
43 |
44 | value, err := db.conn.Get(b64Key)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | var decoded []byte
50 | decoded, err = base64.StdEncoding.DecodeString(value)
51 | return &decoded, err
52 | }
53 |
54 | func (db *berkleyDb) ReadAll(f func(key, value *[]byte)) error {
55 | iter, err := db.conn.Cursor()
56 | if err != nil {
57 | return err
58 | }
59 |
60 | var b64Key, b64Value string
61 | for {
62 | b64Key, b64Value, err = iter.GetNext()
63 | if err != nil {
64 | break
65 | }
66 | key, err := base64.StdEncoding.DecodeString(b64Key)
67 | if err != nil {
68 | break
69 | }
70 | value, err := base64.StdEncoding.DecodeString(b64Value)
71 | if err != nil {
72 | break
73 | }
74 | f(&key, &value)
75 | }
76 |
77 | return err
78 | }
79 |
80 | func (db *berkleyDb) Delete(key *[]byte) error {
81 | b64Key := base64.StdEncoding.EncodeToString(*key)
82 | return db.conn.Delete(b64Key)
83 | }
84 |
85 | func (db *berkleyDb) Close() error {
86 | return db.conn.Close()
87 | }
88 |
--------------------------------------------------------------------------------
/docs/quorum-architecture.xml:
--------------------------------------------------------------------------------
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=
--------------------------------------------------------------------------------
/docker/quorum-crux/istanbul-genesis.json:
--------------------------------------------------------------------------------
1 | {
2 | "config": {
3 | "chainId": 2017,
4 | "homesteadBlock": 1,
5 | "eip150Block": 2,
6 | "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
7 | "eip155Block": 3,
8 | "eip158Block": 3,
9 | "istanbul": {
10 | "epoch": 30000,
11 | "policy": 0
12 | }
13 | },
14 | "nonce": "0x0",
15 | "timestamp": "0x5b68584d",
16 | "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f89af85494c816ef5f432f9aff47b74120a9d6b41ea9a3bb1b946e98452e06d5158133490981a66f626538ec2c44947785902068b03c3820f8168af31845ffb313bdcd94c5263ce01b7c1fc0bc81d348a84df9f7bca54492b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0",
17 | "gasLimit": "0x1312D00",
18 | "difficulty": "0x1",
19 | "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
20 | "coinbase": "0x0000000000000000000000000000000000000000",
21 | "alloc": {
22 | "6e98452e06d5158133490981a66f626538ec2c44": {
23 | "balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
24 | },
25 | "7785902068b03c3820f8168af31845ffb313bdcd": {
26 | "balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
27 | },
28 | "c5263ce01b7c1fc0bc81d348a84df9f7bca54492": {
29 | "balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
30 | },
31 | "c816ef5f432f9aff47b74120a9d6b41ea9a3bb1b": {
32 | "balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
33 | },
34 | "ed9d02e382b34818e88b88a309c7fe71e65f419d": {
35 | "balance": "1000000000000000000000000000"
36 | },
37 | "ca843569e3427144cead5e4d5999a3d0ccf92b8e": {
38 | "balance": "1000000000000000000000000000"
39 | },
40 | "0fbdc686b912d7722dc86510934589e0aaf3b55a": {
41 | "balance": "1000000000000000000000000000"
42 | },
43 | "9186eb3d20cbd1f5f992a950d808c4495153abd5": {
44 | "balance": "1000000000000000000000000000"
45 | }
46 | },
47 | "number": "0x0",
48 | "gasUsed": "0x0",
49 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
50 | }
51 |
--------------------------------------------------------------------------------
/api/client.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // SendRequest sends a new transaction to the enclave for storage and propagation to the provided
4 | // recipients.
5 | type SendRequest struct {
6 | // Payload is the transaction payload data we wish to store.
7 | Payload string `json:"payload"`
8 | // From is the sender node identification.
9 | From string `json:"from"`
10 | // To is a list of the recipient nodes that should be privy to this transaction payload.
11 | To []string `json:"to"`
12 | }
13 |
14 | // SendResponse is the response to the SendRequest
15 | type SendResponse struct {
16 | // Key is the key that can be used to retrieve the submitted transaction.
17 | Key string `json:"key"`
18 | }
19 |
20 | // ReceiveRequest
21 | type ReceiveRequest struct {
22 | Key string `json:"key"`
23 | To string `json:"to"`
24 | }
25 |
26 | // ReceiveResponse returns the raw payload associated with the ReceiveRequest.
27 | type ReceiveResponse struct {
28 | Payload string `json:"payload"`
29 | }
30 |
31 | // DeleteRequest deletes the entry matching the given key from the enclave.
32 | type DeleteRequest struct {
33 | Key string `json:"key"`
34 | }
35 |
36 | // ResendRequest is used to resend previous transactions.
37 | // There are two types of supported request.
38 | // 1. All transactions associated with a node, in which case the Key field should be omitted.
39 | // 2. A specific transaction with the given key value.
40 | type ResendRequest struct {
41 | // Type is the resend request type. It should be either "all" or "individual" depending on if
42 | // you want to request an individual transaction, or all transactions associated with a node.
43 | Type string `json:"type"`
44 | PublicKey string `json:"publicKey"`
45 | Key string `json:"key,omitempty"`
46 | }
47 |
48 | type UpdatePartyInfo struct {
49 | Url string `json:"url"`
50 | Recipients map[string][]byte `json:"recipients"`
51 | Parties map[string]bool `json:"parties"`
52 | }
53 |
54 | type PartyInfoResponse struct {
55 | Payload []byte `json:"payload"`
56 | }
57 | type PrivateKeyBytes struct {
58 | Bytes string `json:"bytes"`
59 | }
60 |
61 | // PrivateKey is a container for a private key.
62 | type PrivateKey struct {
63 | Data PrivateKeyBytes `json:"data"`
64 | Type string `json:"type"`
65 | }
66 |
--------------------------------------------------------------------------------
/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | const configFile = "config_testdata.conf"
9 |
10 | func TestInitFlags(t *testing.T) {
11 | InitFlags()
12 | conf := AllSettings()
13 | expected := map[string]interface{}{
14 | Port: -1,
15 | Verbosity: 1,
16 | BerkeleyDb: false,
17 | GenerateKeys: "",
18 | AlwaysSendTo: "",
19 | Storage: "crux.db",
20 | WorkDir: ".",
21 | Url: "",
22 | PublicKeys: "",
23 | OtherNodes: "",
24 | PrivateKeys: "",
25 | Socket: "crux.ipc",
26 | }
27 |
28 | verifyConfig(t, conf, expected)
29 | }
30 |
31 | func TestLoadConfig(t *testing.T) {
32 | err := LoadConfig(configFile)
33 |
34 | if err != nil {
35 | t.Fatalf("Unable to load config file: %s, %s", configFile, err)
36 | }
37 |
38 | conf := AllSettings()
39 |
40 | expected := map[string]interface{}{
41 | Verbosity: 1,
42 | AlwaysSendTo: []interface{}{},
43 | TlsServerChain: []interface{}{},
44 | Storage: "dir:storage",
45 | WorkDir: "data",
46 | Url: "http://127.0.0.1:9001/",
47 | TlsServerTrust: "tofu",
48 | PublicKeys: []interface{}{"foo.pub"},
49 | OtherNodes: []interface{}{"http://127.0.0.1:9000/"},
50 | TlsKnownServers: "tls-known-servers",
51 | TlsClientCert: "tls-client-cert.pem",
52 | PrivateKeys: []interface{}{"foo.key"},
53 | TlsServerCert: "tls-server-cert.pem",
54 | Tls: "strict",
55 | TlsKnownClients: "tls-known-clients",
56 | TlsClientChain: []interface{}{},
57 | TlsClientKey: "tls-client-key.pem",
58 | Socket: "constellation.ipc",
59 | TlsClientTrust: "ca-or-tofu",
60 | TlsServerKey: "tls-server-key.pem",
61 | Port: 9001,
62 | }
63 |
64 | verifyConfig(t, conf, expected)
65 | }
66 |
67 | func verifyConfig(t *testing.T, conf map[string]interface{}, expected map[string]interface{}) {
68 | for expK, expV := range expected {
69 | //if conf[key] != value {
70 | if actV, ok := conf[expK]; ok {
71 | var eq bool
72 | switch actV.(type) { // we cannot use == for equality with []interface{}
73 | case []interface{}:
74 | eq = reflect.DeepEqual(actV, expV)
75 | default:
76 | eq = actV == expV
77 | }
78 | if !eq {
79 | t.Errorf("Key: %s with value %v could not be found", expK, expV)
80 | }
81 | }
82 | }
83 | }
84 |
85 | func TestGetBoolConfig(t *testing.T) {
86 | if GetBool(Verbosity) != true {
87 | t.Errorf("Verbosity is %t", GetBool(Verbosity))
88 | }
89 | }
90 |
91 | func TestGetIntConfig(t *testing.T) {
92 | if GetInt(Port) != 9001 {
93 | t.Errorf("Port num 9001 is expected but we got %d", GetInt(Port))
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/test/client_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "encoding/base64"
5 | "github.com/blk-io/chimera-api/chimera"
6 | "golang.org/x/net/context"
7 | "google.golang.org/grpc"
8 | "reflect"
9 | "testing"
10 | )
11 |
12 | const sender = "zSifTnkv5r4K67Dq304eVcM4FpxGfHLe1yTCBm0/7wg="
13 | const receiver = "I/EbshW61ykJ+qTivXPaKyQ5WmQDUFfMNGEBj2E2uUs="
14 |
15 | var payload = []byte("payload")
16 |
17 | func TestIntegration(t *testing.T) {
18 | var conn1 *grpc.ClientConn
19 | var conn2 *grpc.ClientConn
20 | // Initiate a connection with the first server
21 | conn1, err := grpc.Dial(":9020", grpc.WithInsecure())
22 | if err != nil {
23 | t.Fatalf("did not connect: %s", err)
24 | }
25 | defer conn1.Close()
26 | c1 := chimera.NewClientClient(conn1)
27 | // Initiate a connection with the second server
28 | conn2, err = grpc.Dial(":9025", grpc.WithInsecure())
29 | if err != nil {
30 | t.Fatalf("did not connect: %s", err)
31 | }
32 | defer conn2.Close()
33 | c2 := chimera.NewClientClient(conn2)
34 |
35 | Upcheckresponse1, err := c1.Upcheck(context.Background(), &chimera.UpCheckResponse{Message: "foo"})
36 | if err != nil {
37 | t.Fatalf("error when calling Upcheck: %s", err)
38 | }
39 | t.Logf("Response from server: %s", Upcheckresponse1.Message)
40 |
41 | Upcheckresponse2, err := c2.Upcheck(context.Background(), &chimera.UpCheckResponse{Message: "foo"})
42 | if err != nil {
43 | t.Fatalf("error when calling Upcheck: %s", err)
44 | }
45 | t.Logf("Response from server: %s", Upcheckresponse2.Message)
46 |
47 | sendReqs := []chimera.SendRequest{
48 | {
49 | Payload: []byte("payload"),
50 | From: sender,
51 | To: []string{receiver},
52 | },
53 | {
54 | Payload: []byte("test"),
55 | To: []string{},
56 | },
57 | {
58 | Payload: []byte("blk-io crux"),
59 | },
60 | }
61 |
62 | sendResponse := chimera.SendResponse{}
63 | for _, sendReq := range sendReqs {
64 | sendResp, err := c1.Send(context.Background(), &sendReq)
65 | if err != nil {
66 | t.Fatalf("gRPC send failed with %s", err)
67 | }
68 | sendResponse = chimera.SendResponse{Key: sendResp.Key}
69 | t.Logf("The response for Send request is: %s", base64.StdEncoding.EncodeToString(sendResponse.Key))
70 |
71 | recResp, err := c1.Receive(context.Background(), &chimera.ReceiveRequest{Key: sendResponse.Key, To: receiver})
72 | if err != nil {
73 | t.Fatalf("gRPC receive failed with %s", err)
74 | }
75 | receiveResponse := chimera.ReceiveResponse{Payload: recResp.Payload}
76 | if !reflect.DeepEqual(receiveResponse.Payload, sendReq.Payload) {
77 | t.Fatalf("handler returned unexpected response: %v, expected: %v\n", receiveResponse.Payload, sendReq.Payload)
78 | } else {
79 | t.Logf("The payload return is %v", receiveResponse.Payload)
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/api/encoding_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/blk-io/crux/utils"
5 | "github.com/kevinburke/nacl"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestEncodePayload(t *testing.T) {
11 |
12 | epl := EncryptedPayload{
13 | Sender: nacl.NewKey(),
14 | CipherText: []byte("C1ph3r T3xt"),
15 | Nonce: nacl.NewNonce(),
16 | RecipientBoxes: [][]byte{[]byte("B0x1"), []byte("B0x2")},
17 | RecipientNonce: nacl.NewNonce(),
18 | }
19 |
20 | encoded := EncodePayload(epl)
21 | decoded := DecodePayload(encoded)
22 |
23 | if !reflect.DeepEqual(epl, decoded) {
24 | t.Errorf("Decoded payload: %v does not match input %v", decoded, epl)
25 | }
26 | }
27 |
28 | func TestEncodePayloadWithRecipients(t *testing.T) {
29 |
30 | epls := []EncryptedPayload{
31 | {
32 | Sender: nacl.NewKey(),
33 | CipherText: []byte("C1ph3r T3xt1"),
34 | Nonce: nacl.NewNonce(),
35 | RecipientBoxes: [][]byte{[]byte("B0x1"), []byte("B0x2"), []byte("B0x3")},
36 | RecipientNonce: nacl.NewNonce(),
37 | },
38 | {
39 | Sender: nacl.NewKey(),
40 | CipherText: []byte("C1ph3r T3xt2"),
41 | Nonce: nacl.NewNonce(),
42 | RecipientBoxes: [][]byte{[]byte("B0x1")},
43 | RecipientNonce: nacl.NewNonce(),
44 | },
45 | }
46 |
47 | recipients := [][][]byte{
48 | {
49 | (*nacl.NewKey())[:],
50 | (*nacl.NewKey())[:],
51 | (*nacl.NewKey())[:],
52 | },
53 | {}, // Recipients may be empty
54 | }
55 |
56 | for i, epl := range epls {
57 | encoded := EncodePayloadWithRecipients(epl, recipients[i])
58 | decodedEpl, decodedRecipients := DecodePayloadWithRecipients(encoded)
59 |
60 | if !reflect.DeepEqual(epl, decodedEpl) {
61 | t.Errorf("Decoded partyInfo: %v does not match input %v", decodedEpl, epl)
62 | }
63 |
64 | if !reflect.DeepEqual(recipients[i], decodedRecipients) {
65 | t.Errorf("Decoded partyInfo: %v does not match input %v",
66 | decodedRecipients, recipients[i])
67 | }
68 | }
69 | }
70 |
71 | func TestEncodePartyInfo(t *testing.T) {
72 |
73 | pi := PartyInfo{
74 | url: "https://127.0.0.4:9004/",
75 | recipients: map[[nacl.KeySize]byte]string{
76 | toKey("ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="): "https://127.0.0.7:9007/",
77 | toKey("BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo="): "https://127.0.0.1:9001/",
78 | toKey("QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="): "https://127.0.0.2:9002/",
79 | toKey("1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg="): "https://127.0.0.3:9003/",
80 | toKey("UfNSeSGySeKg11DVNEnqrUtxYRVor4+CvluI8tVv62Y="): "https://127.0.0.6:9006/",
81 | toKey("oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8="): "https://127.0.0.4:9004/",
82 | toKey("R56gy4dn24YOjwyesTczYa8m5xhP6hF2uTMCju/1xkY="): "https://127.0.0.5:9005/",
83 | },
84 | parties: map[string]bool{
85 | "https://127.0.0.5:9005/": true,
86 | "https://127.0.0.3:9003/": true,
87 | "https://127.0.0.1:9001/": true,
88 | "https://127.0.0.7:9007/": true,
89 | "https://127.0.0.6:9006/": true,
90 | "https://127.0.0.4:9004/": true,
91 | "https://127.0.0.2:9002/": true,
92 | },
93 | }
94 |
95 | runEncodePartyInfoTest(t, pi)
96 | }
97 |
98 | func runEncodePartyInfoTest(t *testing.T, pi PartyInfo) {
99 | encoded := EncodePartyInfo(pi)
100 | decoded, err := DecodePartyInfo(encoded)
101 |
102 | if err != nil {
103 | t.Fatalf("Unable to decode party info: %v", err)
104 | }
105 |
106 | if !reflect.DeepEqual(pi, decoded) {
107 | t.Errorf("Decoded partyInfo: %v does not match input %v", decoded, pi)
108 | }
109 | }
110 |
111 | func toKey(encodedKey string) [nacl.KeySize]byte {
112 | key, _ := utils.LoadBase64Key(encodedKey)
113 | return *key
114 | }
115 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | // Package config provides the configuration settings to be used by the application at runtime
2 | package config
3 |
4 | import (
5 | "flag"
6 | "fmt"
7 | "github.com/spf13/pflag"
8 | "github.com/spf13/viper"
9 | "os"
10 | )
11 |
12 | const (
13 | Verbosity = "verbosity"
14 | VerbosityShorthand = "v"
15 | AlwaysSendTo = "alwayssendto"
16 | Storage = "storage"
17 | WorkDir = "workdir"
18 | Url = "url"
19 | OtherNodes = "othernodes"
20 | PublicKeys = "publickeys"
21 | PrivateKeys = "privatekeys"
22 | Port = "port"
23 | Socket = "socket"
24 |
25 | GenerateKeys = "generate-keys"
26 |
27 | BerkeleyDb = "berkeleydb"
28 | UseGRPC = "grpc"
29 | GrpcJsonPort = "grpcport"
30 | NetworkInterface = "networkinterface"
31 |
32 | Tls = "tls"
33 | TlsServerChain = "tlsserverchain"
34 | TlsServerTrust = "tlsservertrust"
35 | TlsKnownServers = "tlsknownservers"
36 | TlsClientCert = "tlsclientcert"
37 | TlsServerCert = "tlsservercert"
38 | TlsKnownClients = "tlsknownclients"
39 | TlsClientChain = "tlsclientchain"
40 | TlsClientKey = "tlsclientkey"
41 | TlsClientTrust = "tlsclienttrust"
42 | TlsServerKey = "tlsserverkey"
43 | )
44 |
45 | // InitFlags initializes all supported command line flags.
46 | func InitFlags() {
47 | flag.String(GenerateKeys, "", "Generate a new keypair")
48 | flag.String(Url, "", "The URL to advertise to other nodes (reachable by them)")
49 | flag.Int(Port, -1, "The local port to listen on")
50 | flag.String(WorkDir, ".", "The folder to put stuff in ")
51 | flag.String(Socket, "crux.ipc", "IPC socket to create for access to the Private API")
52 | flag.String(OtherNodes, "", "\"Boot nodes\" to connect to to discover the network")
53 | flag.String(PublicKeys, "", "Public keys hosted by this node")
54 | flag.String(PrivateKeys, "", "Private keys hosted by this node")
55 | flag.String(Storage, "crux.db", "Database storage file name")
56 | flag.Bool(BerkeleyDb, false,
57 | "Use Berkeley DB for working with an existing Constellation data store [experimental]")
58 |
59 | flag.Int(Verbosity, 1, "Verbosity level of logs (0=fatal, 1=warn, 2=info, 3=debug)")
60 | flag.Int(VerbosityShorthand, 1, "Verbosity level of logs (shorthand)")
61 | flag.String(AlwaysSendTo, "", "List of public keys for nodes to send all transactions too")
62 | flag.Bool(UseGRPC, true, "Use gRPC server")
63 | flag.Bool(Tls, false, "Use TLS to secure HTTP communications")
64 | flag.String(TlsServerCert, "", "The server certificate to be used")
65 | flag.String(TlsServerKey, "", "The server private key")
66 | flag.Int(GrpcJsonPort, -1, "The local port to listen on for JSON extensions of gRPC")
67 | flag.String(NetworkInterface, "localhost", "The network interface to bind the server to")
68 |
69 | // storage not currently supported as we use LevelDB
70 |
71 | pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
72 | viper.BindPFlags(pflag.CommandLine) // Binding the flags to test the initial configuration
73 | }
74 |
75 | // Usage prints usage instructions to the console.
76 | func Usage() {
77 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
78 | fmt.Fprintf(os.Stderr, " %-25s%s\n", "crux.config", "Optional config file")
79 | pflag.PrintDefaults()
80 | }
81 |
82 | // ParseCommandLine parses all provided command line arguments.
83 | func ParseCommandLine() {
84 | pflag.Parse()
85 | viper.BindPFlags(pflag.CommandLine)
86 | }
87 |
88 | // LoadConfig loads all configuration settings in the provided configPath location.
89 | func LoadConfig(configPath string) error {
90 | viper.SetConfigType("hcl")
91 | viper.SetConfigFile(configPath)
92 | return viper.ReadInConfig()
93 | }
94 |
95 | func AllSettings() map[string]interface{} {
96 | return viper.AllSettings()
97 | }
98 |
99 | func GetBool(key string) bool {
100 | return viper.GetBool(key)
101 | }
102 |
103 | func GetInt(key string) int {
104 | return viper.GetInt(key)
105 | }
106 |
107 | func GetString(key string) string {
108 | return viper.GetString(key)
109 | }
110 |
111 | func GetStringSlice(key string) []string {
112 | return viper.GetStringSlice(key)
113 | }
114 |
--------------------------------------------------------------------------------
/crux.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blk-io/crux/api"
5 | "github.com/blk-io/crux/config"
6 | "github.com/blk-io/crux/enclave"
7 | "github.com/blk-io/crux/server"
8 | "github.com/blk-io/crux/storage"
9 | log "github.com/sirupsen/logrus"
10 | "net/http"
11 | "os"
12 | "path"
13 | "strings"
14 | "time"
15 | )
16 |
17 | func main() {
18 |
19 | config.InitFlags()
20 |
21 | args := os.Args
22 | if len(args) == 1 {
23 | exit()
24 | }
25 |
26 | for _, arg := range args[1:] {
27 | if strings.Contains(arg, ".conf") {
28 | err := config.LoadConfig(arg)
29 | if err != nil {
30 | log.Fatalln(err)
31 | }
32 | break
33 | }
34 | }
35 | config.ParseCommandLine()
36 |
37 | verbosity := 1
38 | if config.GetInt(config.Verbosity) > config.GetInt(config.VerbosityShorthand) {
39 | verbosity = config.GetInt(config.Verbosity)
40 | } else {
41 | verbosity = config.GetInt(config.VerbosityShorthand)
42 | }
43 |
44 | var level log.Level
45 |
46 | switch verbosity {
47 | case 0:
48 | level = log.FatalLevel
49 | case 1:
50 | level = log.WarnLevel
51 | case 2:
52 | level = log.InfoLevel
53 | default:
54 | level = log.DebugLevel
55 | }
56 | log.SetLevel(level)
57 |
58 | keyFile := config.GetString(config.GenerateKeys)
59 | if keyFile != "" {
60 | err := enclave.DoKeyGeneration(keyFile)
61 | if err != nil {
62 | log.Fatalln(err)
63 | }
64 | log.Printf("Key pair successfully written to %s", keyFile)
65 | os.Exit(0)
66 | }
67 |
68 | workDir := config.GetString(config.WorkDir)
69 | dbStorage := config.GetString(config.Storage)
70 | ipcFile := config.GetString(config.Socket)
71 | storagePath := path.Join(workDir, dbStorage)
72 | ipcPath := path.Join(workDir, ipcFile)
73 | var db storage.DataStore
74 | var err error
75 | if config.GetBool(config.BerkeleyDb) {
76 | db, err = storage.InitBerkeleyDb(storagePath)
77 | } else {
78 | db, err = storage.InitLevelDb(storagePath)
79 | }
80 |
81 | if err != nil {
82 | log.Fatalf("Unable to initialise storage, error: %v", err)
83 | }
84 | defer db.Close()
85 |
86 | allOtherNodes := config.GetString(config.OtherNodes)
87 | otherNodes := strings.Split(allOtherNodes, ",")
88 | url := config.GetString(config.Url)
89 | if url == "" {
90 | log.Fatalln("URL must be specified")
91 | }
92 | port := config.GetInt(config.Port)
93 | if port < 0 {
94 | log.Fatalln("Port must be specified")
95 | }
96 | httpClient := &http.Client{
97 | Timeout: time.Second * 10,
98 | }
99 | grpc := config.GetBool(config.UseGRPC)
100 |
101 | pi := api.InitPartyInfo(url, otherNodes, httpClient, grpc)
102 |
103 | privKeys := config.GetString(config.PrivateKeys)
104 | pubKeys := config.GetString(config.PublicKeys)
105 | pubKeyFiles := strings.Split(pubKeys, ",")
106 | privKeyFiles := strings.Split(privKeys, ",")
107 |
108 | if len(privKeyFiles) != len(pubKeyFiles) {
109 | log.Fatalln("Private keys provided must have corresponding public keys")
110 | }
111 |
112 | if len(privKeyFiles) == 0 {
113 | log.Fatalln("Node key files must be provided")
114 | }
115 |
116 | for i, keyFile := range privKeyFiles {
117 | privKeyFiles[i] = path.Join(workDir, keyFile)
118 | }
119 |
120 | for i, keyFile := range pubKeyFiles {
121 | pubKeyFiles[i] = path.Join(workDir, keyFile)
122 | }
123 |
124 | enc := enclave.Init(db, pubKeyFiles, privKeyFiles, pi, http.DefaultClient, grpc)
125 |
126 | pi.RegisterPublicKeys(enc.PubKeys)
127 |
128 | tls := config.GetBool(config.Tls)
129 | var tlsCertFile, tlsKeyFile string
130 | if tls {
131 | servCert := config.GetString(config.TlsServerCert)
132 | servKey := config.GetString(config.TlsServerKey)
133 |
134 | if (len(servCert) != len(servKey)) || (len(servCert) <= 0) {
135 | log.Fatalf("Please provide server certificate and key for TLS %s %s %d ", servKey, servCert, len(servCert))
136 | }
137 |
138 | tlsCertFile = path.Join(workDir, servCert)
139 | tlsKeyFile = path.Join(workDir, servKey)
140 | }
141 | grpcJsonport := config.GetInt(config.GrpcJsonPort)
142 | networkInterface := config.GetString(config.NetworkInterface)
143 | _, err = server.Init(enc, networkInterface, port, ipcPath, grpc, grpcJsonport, tls, tlsCertFile, tlsKeyFile)
144 | if err != nil {
145 | log.Fatalf("Error starting server: %v\n", err)
146 | }
147 |
148 | pi.PollPartyInfo()
149 |
150 | select {}
151 | }
152 |
153 | func exit() {
154 | config.Usage()
155 | os.Exit(1)
156 | }
157 |
--------------------------------------------------------------------------------
/server/proto_server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "github.com/blk-io/chimera-api/chimera"
6 | "github.com/blk-io/crux/utils"
7 | "github.com/grpc-ecosystem/grpc-gateway/runtime"
8 | log "github.com/sirupsen/logrus"
9 | "golang.org/x/net/context"
10 | "google.golang.org/grpc"
11 | "google.golang.org/grpc/credentials"
12 | "net"
13 | "net/http"
14 | )
15 |
16 | func (tm *TransactionManager) startRpcServer(networkInterface string, port int, grpcJsonPort int, ipcPath string, tls bool, certFile, keyFile string) error {
17 | lis, err := utils.CreateIpcSocket(ipcPath)
18 | if err != nil {
19 | log.Fatalf("failed to listen: %v", err)
20 | }
21 | s := Server{Enclave: tm.Enclave}
22 | grpcServer := grpc.NewServer()
23 | chimera.RegisterClientServer(grpcServer, &s)
24 | go func() {
25 | log.Fatal(grpcServer.Serve(lis))
26 | }()
27 |
28 | go func() error {
29 | var err error
30 | if tls {
31 | err = tm.startRestServerTLS(networkInterface, port, certFile, keyFile, certFile)
32 | } else {
33 | err = tm.startRestServer(networkInterface, port)
34 | }
35 | if grpcJsonPort != -1 {
36 | if tls {
37 | err = tm.startJsonServerTLS(networkInterface, port, grpcJsonPort, certFile, keyFile, certFile)
38 | } else {
39 | err = tm.startJsonServer(networkInterface, port, grpcJsonPort)
40 | }
41 | }
42 | if err != nil {
43 | log.Fatalf("failed to start gRPC REST server: %s", err)
44 | }
45 | return err
46 | }()
47 |
48 | return err
49 | }
50 |
51 | func (tm *TransactionManager) startJsonServer(networkInterface string, port int, grpcJsonPort int) error {
52 | address := fmt.Sprintf("%s:%d", networkInterface, grpcJsonPort)
53 | ctx := context.Background()
54 | ctx, cancel := context.WithCancel(ctx)
55 | defer cancel()
56 | mux := runtime.NewServeMux()
57 | opts := []grpc.DialOption{grpc.WithInsecure()}
58 | err := chimera.RegisterClientHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", networkInterface, port), opts)
59 | if err != nil {
60 | return fmt.Errorf("could not register service: %s", err)
61 | }
62 | log.Printf("starting HTTP/1.1 REST server on %s", address)
63 | err = http.ListenAndServe(address, mux)
64 | if err != nil {
65 | return fmt.Errorf("could not listen on %s due to: %s", address, err)
66 | }
67 | return nil
68 | }
69 |
70 | func (tm *TransactionManager) startRestServer(networkInterface string, port int) error {
71 | grpcAddress := fmt.Sprintf("%s:%d", networkInterface, port)
72 | lis, err := net.Listen("tcp", grpcAddress)
73 | if err != nil {
74 | panic(err)
75 | }
76 | s := Server{Enclave: tm.Enclave}
77 | grpcServer := grpc.NewServer()
78 | chimera.RegisterClientServer(grpcServer, &s)
79 | go func() {
80 | log.Fatal(grpcServer.Serve(lis))
81 | }()
82 | return nil
83 | }
84 |
85 | func (tm *TransactionManager) startJsonServerTLS(networkInterface string, port int, grpcJsonPort int, certFile, keyFile, ca string) error {
86 | address := fmt.Sprintf("%s:%d", networkInterface, grpcJsonPort)
87 | ctx := context.Background()
88 | ctx, cancel := context.WithCancel(ctx)
89 | defer cancel()
90 | mux := runtime.NewServeMux()
91 | creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
92 | err = chimera.RegisterClientHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", networkInterface, port), []grpc.DialOption{grpc.WithTransportCredentials(creds)})
93 | if err != nil {
94 | log.Fatalf("could not register service Ping: %s", err)
95 | return err
96 | }
97 | http.ListenAndServe(address, mux)
98 | log.Printf("started HTTPS REST server on %s", address)
99 | return nil
100 | }
101 |
102 | func (tm *TransactionManager) startRestServerTLS(networkInterface string, port int, certFile, keyFile, ca string) error {
103 | grpcAddress := fmt.Sprintf("%s:%d", networkInterface, port)
104 | lis, err := net.Listen("tcp", grpcAddress)
105 | if err != nil {
106 | log.Fatalf("failed to start gRPC REST server: %s", err)
107 | }
108 | s := Server{Enclave: tm.Enclave}
109 | creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
110 | opts := []grpc.ServerOption{grpc.Creds(creds)}
111 | if err != nil {
112 | log.Fatalf("failed to load credentials : %v", err)
113 | }
114 | grpcServer := grpc.NewServer(opts...)
115 | chimera.RegisterClientServer(grpcServer, &s)
116 | go func() {
117 | log.Fatal(grpcServer.Serve(lis))
118 | }()
119 | return nil
120 | }
121 |
122 | func GetFreePort(networkInterface string) (int, error) {
123 | addr, err := net.ResolveTCPAddr("tcp", networkInterface + ":0")
124 | if err != nil {
125 | return 0, err
126 | }
127 |
128 | l, err := net.ListenTCP("tcp", addr)
129 | if err != nil {
130 | return 0, err
131 | }
132 | defer l.Close()
133 | return l.Addr().(*net.TCPAddr).Port, nil
134 | }
135 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # The import path is where your repository can be found.
2 | # To import subpackages, always prepend the full import path.
3 | # If you change this, run `make clean`. Read more: https://git.io/vM7zV
4 | IMPORT_PATH := github.com/blk-io/crux
5 |
6 | # V := 1 # When V is set, print commands and build progress.
7 |
8 | # Space separated patterns of packages to skip in list, test, format.
9 | IGNORED_PACKAGES := /vendor/
10 |
11 | .PHONY: all
12 | all: build
13 |
14 | .PHONY: build
15 | build: .GOPATH/.ok
16 | $Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)
17 |
18 | ### Code not in the repository root? Another binary? Add to the path like this.
19 | # .PHONY: otherbin
20 | # otherbin: .GOPATH/.ok
21 | # $Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/otherbin
22 |
23 | ##### =====> Utility targets <===== #####
24 |
25 | .PHONY: clean test list cover format
26 |
27 | clean:
28 | $Q rm -rf bin .GOPATH
29 |
30 | test: .GOPATH/.ok
31 | $Q ./bin/crux --url=http://127.0.0.1:9020/ --port=9020 --workdir=test/test1 --publickeys=testdata/key.pub --privatekeys=testdata/key &
32 | $Q ./bin/crux --url=http://127.0.0.1:9025/ --port=9025 --workdir=test/test2 --publickeys=testdata/rcpt1.pub --privatekeys=testdata/rcpt1 &
33 | $Q go test $(if $V,-v) -i -race $(allpackages) # install -race libs to speed up next run
34 | ifndef CI
35 | $Q go vet $(allpackages)
36 | $Q GODEBUG=cgocheck=2 go test -race $(allpackages)
37 | else
38 | $Q ( go vet $(allpackages); echo $$? ) | \
39 | tee .GOPATH/test/vet.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/vet.txt)
40 | $Q ( GODEBUG=cgocheck=2 go test -v -race $(allpackages); echo $$? ) | \
41 | tee .GOPATH/test/output.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/output.txt)
42 | endif
43 | $Q pkill crux
44 |
45 | list: .GOPATH/.ok
46 | @echo $(allpackages)
47 |
48 | cover: bin/gocovmerge .GOPATH/.ok
49 | @echo "NOTE: make cover does not exit 1 on failure, don't use it to check for tests success!"
50 | $Q rm -f .GOPATH/cover/*.out .GOPATH/cover/all.merged
51 | $(if $V,@echo "-- go test -coverpkg=./... -coverprofile=.GOPATH/cover/... ./...")
52 | @for MOD in $(allpackages); do \
53 | go test -coverpkg=`echo $(allpackages)|tr " " ","` \
54 | -coverprofile=.GOPATH/cover/unit-`echo $$MOD|tr "/" "_"`.out \
55 | $$MOD 2>&1 | grep -v "no packages being tested depend on"; \
56 | done
57 | $Q ./bin/gocovmerge .GOPATH/cover/*.out > .GOPATH/cover/all.merged
58 | ifndef CI
59 | $Q go tool cover -html .GOPATH/cover/all.merged
60 | else
61 | $Q go tool cover -html .GOPATH/cover/all.merged -o .GOPATH/cover/all.html
62 | endif
63 | @echo ""
64 | @echo "=====> Total test coverage: <====="
65 | @echo ""
66 | $Q go tool cover -func .GOPATH/cover/all.merged
67 |
68 | format: bin/goimports .GOPATH/.ok
69 | $Q find .GOPATH/src/$(IMPORT_PATH)/ -iname \*.go | grep -v \
70 | -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) | xargs ./bin/goimports -w
71 |
72 | ##### =====> Internals <===== #####
73 |
74 | .PHONY: setup
75 | setup: clean .GOPATH/.ok
76 | @if ! grep "/.GOPATH" .gitignore > /dev/null 2>&1; then \
77 | echo "/.GOPATH" >> .gitignore; \
78 | echo "/bin" >> .gitignore; \
79 | fi
80 | go get -u github.com/golang/dep/cmd/dep
81 | go get -u golang.org/x/tools/cmd/goimports
82 | go get -u github.com/wadey/gocovmerge
83 | @test -f Gopkg.toml || \
84 | (cd $(CURDIR)/.GOPATH/src/$(IMPORT_PATH) && ./bin/dep init)
85 | (cd $(CURDIR)/.GOPATH/src/$(IMPORT_PATH) && ./bin/dep ensure)
86 |
87 | VERSION := $(shell git describe --tags --always --dirty="-dev")
88 | DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC')
89 | VERSION_FLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
90 |
91 | # cd into the GOPATH to workaround ./... not following symlinks
92 | _allpackages = $(shell ( cd $(CURDIR)/.GOPATH/src/$(IMPORT_PATH) && \
93 | GOPATH=$(CURDIR)/.GOPATH go list ./... 2>&1 1>&3 | \
94 | grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) 1>&2 ) 3>&1 | \
95 | grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)))
96 |
97 | # memoize allpackages, so that it's executed only once and only if used
98 | allpackages = $(if $(__allpackages),,$(eval __allpackages := $$(_allpackages)))$(__allpackages)
99 |
100 | export GOPATH := $(CURDIR)/.GOPATH
101 | unexport GOBIN
102 |
103 | Q := $(if $V,,@)
104 |
105 | .GOPATH/.ok:
106 | $Q mkdir -p "$(dir .GOPATH/src/$(IMPORT_PATH))"
107 | $Q ln -s ../../../.. ".GOPATH/src/$(IMPORT_PATH)"
108 | $Q mkdir -p .GOPATH/test .GOPATH/cover
109 | $Q mkdir -p bin
110 | $Q ln -s ../bin .GOPATH/bin
111 | $Q touch $@
112 |
113 | .PHONY: bin/gocovmerge bin/goimports
114 | bin/gocovmerge: .GOPATH/.ok
115 | @test -f ./bin/gocovmerge || \
116 | { echo "Vendored gocovmerge not found, try running 'make setup'..."; exit 1; }
117 | bin/goimports: .GOPATH/.ok
118 | @test -d ./vendor/golang.org/x/tools/cmd/goimports || \
119 | { echo "Vendored goimports not found, try running 'make setup'..."; exit 1; }
120 | $Q go install $(IMPORT_PATH)/vendor/golang.org/x/tools/cmd/goimports
121 |
122 |
--------------------------------------------------------------------------------
/server/server_handler.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/hex"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/blk-io/chimera-api/chimera"
9 | "github.com/blk-io/crux/api"
10 | "github.com/kevinburke/nacl"
11 | log "github.com/sirupsen/logrus"
12 | "golang.org/x/net/context"
13 | )
14 |
15 | type Server struct {
16 | Enclave Enclave
17 | }
18 |
19 | func (s *Server) Version(ctx context.Context, in *chimera.ApiVersion) (*chimera.ApiVersion, error) {
20 | return &chimera.ApiVersion{Version: apiVersion}, nil
21 | }
22 |
23 | func (s *Server) Upcheck(ctx context.Context, in *chimera.UpCheckResponse) (*chimera.UpCheckResponse, error) {
24 | return &chimera.UpCheckResponse{Message: upCheckResponse}, nil
25 | }
26 | func (s *Server) Send(ctx context.Context, in *chimera.SendRequest) (*chimera.SendResponse, error) {
27 | key, err := s.processSend(in.GetFrom(), in.GetTo(), &in.Payload)
28 | var sendResp chimera.SendResponse
29 | if err != nil {
30 | log.Error(err)
31 | } else {
32 | sendResp = chimera.SendResponse{Key: key}
33 | }
34 | return &sendResp, err
35 | }
36 |
37 | func (s *Server) processSend(b64from string, b64recipients []string, payload *[]byte) ([]byte, error) {
38 | log.WithFields(log.Fields{
39 | "b64From": b64from,
40 | "b64Recipients": b64recipients,
41 | "payload": hex.EncodeToString(*payload)}).Debugf(
42 | "Processing send request")
43 |
44 | sender, err := base64.StdEncoding.DecodeString(b64from)
45 | if err != nil {
46 | decodeErrorGRPC("sender", b64from, err)
47 | return nil, err
48 | }
49 |
50 | recipients := make([][]byte, len(b64recipients))
51 | for i, value := range b64recipients {
52 | recipient, err := base64.StdEncoding.DecodeString(value)
53 | if err != nil {
54 | decodeErrorGRPC("recipients", value, err)
55 | return nil, err
56 | } else {
57 | recipients[i] = recipient
58 | }
59 | }
60 |
61 | return s.Enclave.Store(payload, sender, recipients)
62 | }
63 |
64 | func (s *Server) Receive(ctx context.Context, in *chimera.ReceiveRequest) (*chimera.ReceiveResponse, error) {
65 | payload, err := s.processReceive(in.Key, in.To)
66 | var receiveResp chimera.ReceiveResponse
67 | if err != nil {
68 | log.Error(err)
69 | } else {
70 | receiveResp = chimera.ReceiveResponse{Payload: payload}
71 | }
72 | return &receiveResp, err
73 | }
74 |
75 | func (s *Server) processReceive(b64Key []byte, b64To string) ([]byte, error) {
76 | if b64To != "" {
77 | to, err := base64.StdEncoding.DecodeString(b64To)
78 | if err != nil {
79 | return nil, fmt.Errorf("unable to decode to: %s", b64Key)
80 | }
81 |
82 | return s.Enclave.Retrieve(&b64Key, &to)
83 | } else {
84 | return s.Enclave.RetrieveDefault(&b64Key)
85 | }
86 | }
87 |
88 | func (s *Server) UpdatePartyInfo(ctx context.Context, in *chimera.PartyInfo) (*chimera.PartyInfoResponse, error) {
89 | recipients := make(map[[nacl.KeySize]byte]string)
90 | for url, key := range in.Recipients {
91 | var as [32]byte
92 | copy(as[:], key)
93 | recipients[as] = url
94 | }
95 | s.Enclave.UpdatePartyInfoGrpc(in.Url, recipients, in.Parties)
96 | encoded := s.Enclave.GetEncodedPartyInfoGrpc()
97 | var decodedPartyInfo chimera.PartyInfoResponse
98 | err := json.Unmarshal(encoded, &decodedPartyInfo)
99 | if err != nil {
100 | log.Errorf("Unmarshalling failed with %v", err)
101 | }
102 | return &chimera.PartyInfoResponse{Payload: decodedPartyInfo.Payload}, nil
103 | }
104 |
105 | func (s *Server) Push(ctx context.Context, in *chimera.PushPayload) (*chimera.PartyInfoResponse, error) {
106 | sender := new([nacl.KeySize]byte)
107 | nonce := new([nacl.NonceSize]byte)
108 | recipientNonce := new([nacl.NonceSize]byte)
109 | copy((*sender)[:], in.Ep.Sender)
110 | copy((*nonce)[:], in.Ep.Nonce)
111 | copy((*recipientNonce)[:], in.Ep.ReciepientNonce)
112 |
113 | encyptedPayload := api.EncryptedPayload{
114 | Sender: sender,
115 | CipherText: in.Ep.CipherText,
116 | Nonce: nonce,
117 | RecipientBoxes: in.Ep.ReciepientBoxes,
118 | RecipientNonce: recipientNonce,
119 | }
120 |
121 | digestHash, err := s.Enclave.StorePayloadGrpc(encyptedPayload, in.Encoded)
122 | if err != nil {
123 | log.Fatalf("Unable to store payload, error: %s\n", err)
124 | }
125 |
126 | return &chimera.PartyInfoResponse{Payload: digestHash}, nil
127 | }
128 |
129 | func (s *Server) Delete(ctx context.Context, in *chimera.DeleteRequest) (*chimera.DeleteRequest, error) {
130 | var deleteReq chimera.DeleteRequest
131 | err := s.Enclave.Delete(&deleteReq.Key)
132 | if err != nil {
133 | log.Fatalf("Unable to delete payload, error: %s\n", err)
134 | }
135 | return &chimera.DeleteRequest{Key: deleteReq.Key}, nil
136 | }
137 |
138 | func (s *Server) Resend(ctx context.Context, in *chimera.ResendRequest) (*chimera.ResendResponse, error) {
139 | var resendReq chimera.ResendRequest
140 | var err error
141 |
142 | if resendReq.Type == "all" {
143 | err = s.Enclave.RetrieveAllFor(&resendReq.PublicKey)
144 | if err != nil {
145 | log.Fatalf("Invalid body, exited with %s", err)
146 | }
147 | return nil, err
148 | } else if resendReq.Type == "individual" {
149 | var encodedPl *[]byte
150 | encodedPl, err = s.Enclave.RetrieveFor(&resendReq.Key, &resendReq.PublicKey)
151 | if err != nil {
152 | log.Fatalf("Invalid body, exited with %s", err)
153 | return nil, err
154 | }
155 | return &chimera.ResendResponse{Encoded: *encodedPl}, nil
156 | }
157 | return nil, err
158 | }
159 |
160 | func decodeErrorGRPC(name string, value string, err error) {
161 | log.Error(fmt.Sprintf("Invalid request: unable to decode %s: %s, error: %s\n",
162 | name, value, err))
163 | }
164 |
--------------------------------------------------------------------------------
/api/encoding.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/blk-io/crux/utils"
6 | "github.com/kevinburke/nacl"
7 | )
8 |
9 | func EncodePayload(ep EncryptedPayload) []byte {
10 | // constant fields are 216 bytes
11 | encoded := make([]byte, 512)
12 |
13 | offset := 0
14 | encoded, offset = writeSlice((*ep.Sender)[:], encoded, offset)
15 | encoded, offset = writeSlice(ep.CipherText, encoded, offset)
16 | encoded, offset = writeSlice((*ep.Nonce)[:], encoded, offset)
17 | encoded, offset = writeSliceOfSlice(ep.RecipientBoxes, encoded, offset)
18 | encoded, offset = writeSlice((*ep.RecipientNonce)[:], encoded, offset)
19 |
20 | return encoded[:offset]
21 | }
22 |
23 | func DecodePayload(encoded []byte) EncryptedPayload {
24 |
25 | ep := EncryptedPayload{
26 | Sender: new([nacl.KeySize]byte),
27 | Nonce: new([nacl.NonceSize]byte),
28 | RecipientNonce: new([nacl.NonceSize]byte),
29 | }
30 |
31 | offset := 0
32 | offset = readSliceToArray(encoded, offset, (*ep.Sender)[:])
33 | ep.CipherText, offset = readSlice(encoded, offset)
34 | offset = readSliceToArray(encoded, offset, (*ep.Nonce)[:])
35 | ep.RecipientBoxes, offset = readSliceOfSlice(encoded, offset)
36 | offset = readSliceToArray(encoded, offset, (*ep.RecipientNonce)[:])
37 |
38 | return ep
39 | }
40 |
41 | func EncodePayloadWithRecipients(ep EncryptedPayload, recipients [][]byte) []byte {
42 | encoded := make([][]byte, 2)
43 |
44 | encoded[0] = EncodePayload(ep)
45 |
46 | encodedRecipients := make([]byte, 256)
47 | encodedRecipients, recipientsLength := writeSliceOfSlice(recipients, encodedRecipients, 0)
48 | encoded[1] = encodedRecipients[:recipientsLength]
49 |
50 | encoded2, length := writeSliceOfSlice(encoded, make([]byte, 512), 0)
51 | return encoded2[:length]
52 | }
53 |
54 | func DecodePayloadWithRecipients(encoded []byte) (EncryptedPayload, [][]byte) {
55 |
56 | decoded, _ := readSliceOfSlice(encoded, 0)
57 |
58 | ep := DecodePayload(decoded[0])
59 | recipients, _ := readSliceOfSlice(decoded[1], 0)
60 |
61 | return ep, recipients
62 | }
63 |
64 | func EncodePartyInfo(pi PartyInfo) []byte {
65 |
66 | encoded := make([]byte, 256)
67 |
68 | offset := 0
69 |
70 | encoded, offset = writeSlice([]byte(pi.url), encoded, offset)
71 | encoded, offset = writeInt(len(pi.recipients), encoded, offset)
72 |
73 | for recipient, url := range pi.recipients {
74 | tuple := [][]byte{
75 | recipient[:],
76 | []byte(url),
77 | }
78 | encoded, offset = writeSliceOfSlice(tuple, encoded, offset)
79 | }
80 |
81 | parties := make([][]byte, len(pi.parties))
82 | i := 0
83 | for party := range pi.parties {
84 | parties[i] = []byte(party)
85 | i += 1
86 | }
87 | encoded, offset = writeSliceOfSlice(parties, encoded, offset)
88 |
89 | return encoded
90 | }
91 |
92 | func DecodePartyInfo(encoded []byte) (PartyInfo, error) {
93 | pi := PartyInfo{
94 | recipients: make(map[[nacl.KeySize]byte]string),
95 | parties: make(map[string]bool),
96 | }
97 |
98 | url, offset := readSlice(encoded, 0)
99 | pi.url = string(url)
100 |
101 | var size int
102 | size, offset = readInt(encoded, offset)
103 |
104 | for i := 0; i < size; i++ {
105 | var kv [][]byte
106 | kv, offset = readSliceOfSlice(encoded, offset)
107 | key, err := utils.ToKey(kv[0])
108 | if err != nil {
109 | return PartyInfo{}, err
110 | }
111 | pi.recipients[*key] = string(kv[1])
112 | }
113 |
114 | var parties [][]byte
115 | parties, offset = readSliceOfSlice(encoded, offset)
116 | for _, party := range parties {
117 | pi.parties[string(party)] = true
118 | }
119 |
120 | return pi, nil
121 | }
122 |
123 | func writeInt(v int, dest []byte, offset int) ([]byte, int) {
124 | dest = confirmCapacity(dest, offset, 8)
125 | binary.BigEndian.PutUint64(dest[offset:], uint64(v))
126 | return dest, offset + 8
127 | }
128 |
129 | func confirmCapacity(dest []byte, offset, required int) []byte {
130 | length := len(dest)
131 | if length-offset < required {
132 | var newLength int
133 | if required > length {
134 | newLength = utils.NextPowerOf2(required)
135 | } else {
136 | newLength = length
137 | }
138 | return append(dest, make([]byte, newLength)...)
139 | } else {
140 | return dest
141 | }
142 | }
143 |
144 | func readInt(src []byte, offset int) (int, int) {
145 | return int(binary.BigEndian.Uint64(src[offset:])), offset + 8
146 | }
147 |
148 | func writeSlice(src []byte, dest []byte, offset int) ([]byte, int) {
149 | length := len(src)
150 | dest, offset = writeInt(length, dest, offset)
151 |
152 | dest = confirmCapacity(dest, offset, length)
153 | copy(dest[offset:], src)
154 | return dest, offset + length
155 | }
156 |
157 | func readSliceToArray(src []byte, offset int, dest []byte) int {
158 | var length int
159 | length, offset = readInt(src, offset)
160 | offset += copy(dest, src[offset:offset+length])
161 | return offset
162 | }
163 |
164 | func readSlice(src []byte, offset int) ([]byte, int) {
165 | var length int
166 | length, offset = readInt(src, offset)
167 | return src[offset : offset+length], offset + length
168 | }
169 |
170 | func writeSliceOfSlice(src [][]byte, dest []byte, offset int) ([]byte, int) {
171 | length := len(src)
172 | dest, offset = writeInt(length, dest, offset)
173 |
174 | for _, b := range src {
175 | dest, offset = writeSlice(b, dest, offset)
176 | }
177 |
178 | return dest, offset
179 | }
180 |
181 | func readSliceOfSlice(src []byte, offset int) ([][]byte, int) {
182 | arraySize, offset := readInt(src, offset)
183 |
184 | result := make([][]byte, arraySize)
185 | for i := 0; i < arraySize; i++ {
186 | var length int
187 | length, offset = readInt(src, offset)
188 | result[i] = append(
189 | result[i], src[offset:offset+length]...)
190 | offset += length
191 | }
192 | return result, offset
193 | }
194 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Crux
2 |
3 |
4 |
5 |
6 |
7 | Data privacy for Quorum.
8 |
9 | Crux is a secure enclave for Quorum written in Golang.
10 |
11 | It is a replacement for [Constellation](https://github.com/jpmorganchase/constellation/), the
12 | secure enclave component of [Quorum](https://github.com/jpmorganchase/quorum/), written in Haskell.
13 |
14 | ## Getting started
15 |
16 | ### 4-node Quorum network with Crux
17 |
18 | The best way to start is to run the
19 | [Quorum-Crux Docker image](https://github.com/blk-io/crux/tree/master/docker/quorum-crux). This
20 | image runs a 4 node Quorum network using Crux as the secure enclave communicating over gRPC.
21 |
22 | ```bash
23 | git clone https://github.com/blk-io/crux.git
24 | docker-compose -f docker/quorum-crux/docker-compose.yaml up
25 | ```
26 |
27 | Where the node details are as follows:
28 |
29 | | Name | Quorum node address | Account key | Crux node key |
30 | | ------- | ----------------------- | ------------------------------------------ | -------------------------------------------- |
31 | | quorum1 | http://localhost:22001 | 0xed9d02e382b34818e88b88a309c7fe71e65f419d | BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= |
32 | | quorum2 | http://localhost:22002 | 0xca843569e3427144cead5e4d5999a3d0ccf92b8e | QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc= |
33 | | quorum3 | http://localhost:22003 | 0x0fbdc686b912d7722dc86510934589e0aaf3b55a | 1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg= |
34 | | quorum4 | http://localhost:22004 | 0x9186eb3d20cbd1f5f992a950d808c4495153abd5 | oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8= |
35 |
36 | #### local docker
37 | If you want to make changes to e.g. istanbul-start.sh then build the docker image locally:
38 |
39 | docker-compose -f docker-compose-local.yaml up --build
40 |
41 | ### 2-node Crux only-network
42 |
43 | [2 Crux nodes example](https://github.com/blk-io/crux/tree/master/docker/crux) is simple Docker
44 | image to just bring up 2 Crux nodes which communicate with each other.
45 |
46 | ```bash
47 | git clone https://github.com/blk-io/crux.git
48 | docker-compose -f docker/crux/docker-compose.yaml up
49 | ```
50 |
51 | Where the Crux node keys are the same as `quorum1` and `quorum2` above, and are listening on ports
52 | 9001 and 9002 for gRPC requests.
53 |
54 | ### Vagrant VM
55 |
56 | For those of you who are unable to use Docker, you can run the
57 | [7 Nodes Quorum example](https://github.com/blk-io/quorum-examples) which is an updated version
58 | of JP Morgan's Quorum 7 Nodes example using Crux as the secure enclave.
59 |
60 | ### Download the latest binary
61 |
62 | The latest binaries for different platforms are available on the
63 | [release](https://github.com/blk-io/crux/releases/latest) page.
64 |
65 | ## Generating keys
66 |
67 | Each Crux instance requires at least one key-pair to be associated with it. The key-pair is used
68 | to ensure transaction privacy. Crux uses the [NaCl cryptography library](https://nacl.cr.yp.to/).
69 |
70 | You use the `--generate-keys` argument to generate a new key-pair with Crux:
71 |
72 | ```bash
73 | crux --generate-keys myKey
74 | ```
75 |
76 | This will produce two files, named `myKey.key` and `myKey.pub` reflecting the private and public keys
77 | respectively.
78 |
79 | ## Core configuration
80 |
81 | At a minimum, Crux requires the following configuration parameters. This tells the Crux instance
82 | what port it is running on and what ip address it should advertise to other peers.
83 |
84 | Details of at least one key-pair must be provided for the Crux node to store requests on behalf of.
85 |
86 | ```bash
87 | 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/
88 | ```
89 |
90 | ## Build instructions
91 |
92 | If you'd prefer to run just a client, you can build using the below instructions and run as per
93 | the below.
94 |
95 | ```bash
96 | git clone https://github.com/blk-io/crux.git
97 | cd crux
98 | make setup && make
99 | ./bin/crux
100 |
101 | Usage of ./bin/crux:
102 | crux.config Optional config file
103 | --alwayssendto string List of public keys for nodes to send all transactions too
104 | --berkeleydb Use Berkeley DB for working with an existing Constellation data store [experimental]
105 | --generate-keys string Generate a new keypair
106 | --grpc Use gRPC server (default true)
107 | --grpcport int The local port to listen on for JSON extensions of gRPC (default -1)
108 | --networkinterface string The network interface to bind the server to (default "localhost")
109 | --othernodes string "Boot nodes" to connect to to discover the network
110 | --port int The local port to listen on (default -1)
111 | --privatekeys string Private keys hosted by this node
112 | --publickeys string Public keys hosted by this node
113 | --socket string IPC socket to create for access to the Private API (default "crux.ipc")
114 | --storage string Database storage file name (default "crux.db")
115 | --tls Use TLS to secure HTTP communications
116 | --tlsservercert string The server certificate to be used
117 | --tlsserverkey string The server private key
118 | --url string The URL to advertise to other nodes (reachable by them)
119 | -v, --v int Verbosity level of logs (shorthand) (default 1)
120 | --verbosity int Verbosity level of logs (default 1)
121 | --workdir string The folder to put stuff in (default: .) (default ".")
122 | ```
123 |
124 | ## How does it work?
125 |
126 | At present, Crux performs its cryptographic operations in a manner identical to Constellation. You
127 | can read the specifics [here](https://github.com/jpmorganchase/constellation/#how-it-works).
128 |
129 | The two main workflows for handling private transactions are the submission and retrieval
130 | demonstrated below.
131 |
132 | ### New transaction submission
133 |
134 | 
135 |
136 | ### Existing transaction retrieval
137 |
138 | 
139 |
140 | ## Logical architecture
141 |
142 | 
143 |
144 | ## Why Crux?
145 |
146 | *Crux is a constellation located in the southern sky in a bright portion of the Milky Way. It is
147 | among the most easily distinguished constellations, even though it is the smallest of all 88
148 | modern constellations. (Source: [Wikipedia](https://en.wikipedia.org/wiki/Crux))*
149 |
150 | *The critical or transitional moment or issue, a turning point.*
151 |
152 | ## Thanks
153 |
154 | [@patrickmn](https://github.com/patrickmn) the original author of Constellation. Crux would not
155 | exist were it not for his work.
156 |
--------------------------------------------------------------------------------
/docker/quorum-crux/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "3.4"
2 | services:
3 |
4 | node1: &quorum_crux_node
5 | # Pull image down from Docker Hub
6 | image: blkio10/quorum-crux:v1.0.0
7 | # Uncomment the below, and comment out the above line to build the Docker images yourself
8 | # image: blk.io/quorum/quorum-crux
9 | # build:
10 | # context: .
11 | container_name: quorum1
12 | ports:
13 | - 22001:22000
14 | - 21001:21000
15 | - 9001:9000
16 | restart: always
17 | networks:
18 | quorum_net:
19 | ipv4_address: 10.5.0.11
20 | environment:
21 | - 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}
22 | - NODE_KEY=633b2ef3ed5306f05a532eb44eb84858aca470d4fde039c1c988088e48070e64
23 | - CRUX_PUB=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=
24 | - CRUX_PRIV={"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"}
25 | - PORT=9000
26 | - OWN_URL=node1
27 | - GETH_PORT=21000
28 | - GETH_RPC_PORT=22000
29 | - OTHER_NODES=http://node2:9000/
30 | - 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"]
31 |
32 | node2:
33 | <<: *quorum_crux_node
34 | container_name: quorum2
35 | ports:
36 | - 22002:22000
37 | - 21002:21000
38 | - 9002:9000
39 | networks:
40 | quorum_net:
41 | ipv4_address: 10.5.0.12
42 | environment:
43 | - 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}
44 | - NODE_KEY=d5b8dfced693dbb7bf858dce40e3b2a0373696ac88ccf5e1c0052e26b7d77e49
45 | - CRUX_PUB=QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=
46 | - CRUX_PRIV={"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"}
47 | - PORT=9000
48 | - OWN_URL=node2
49 | - GETH_PORT=21000
50 | - GETH_RPC_PORT=22000
51 | - OTHER_NODES=http://node3:9000/
52 | - 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"]
53 |
54 | node3:
55 | <<: *quorum_crux_node
56 | container_name: quorum3
57 | ports:
58 | - 22003:22000
59 | - 9003:9000
60 | - 21003:21000
61 | networks:
62 | quorum_net:
63 | ipv4_address: 10.5.0.13
64 | environment:
65 | - 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}
66 | - NODE_KEY=45d87820ad7792f86592a8e3494c3e78b4755a480cfad1799d6c3f28c3f0d87c
67 | - CRUX_PUB=1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg=
68 | - CRUX_PRIV={"data":{"bytes":"tMxUVR8bX7aq/TbpVHc2QV3SN2iUuExBwefAuFsO0Lg="},"type":"unlocked"}
69 | - PORT=9000
70 | - OWN_URL=node3
71 | - GETH_PORT=21000
72 | - GETH_RPC_PORT=22000
73 | - OTHER_NODES=http://node4:9000/
74 | - 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"]
75 |
76 | node4:
77 | <<: *quorum_crux_node
78 | container_name: quorum4
79 | ports:
80 | - 22004:22000
81 | - 9004:9000
82 | - 21004:21000
83 | networks:
84 | quorum_net:
85 | ipv4_address: 10.5.0.14
86 | environment:
87 | - 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}
88 | - NODE_KEY=daa89d4ae250d24b33847343d0cc0116c48331b81e28514522bb7f77f2be5676
89 | - CRUX_PUB=oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8=
90 | - CRUX_PRIV={"data":{"bytes":"grQjd3dBp4qFs8/5Jdq7xjz++aUx/LXAqISFyPWaCRw="},"type":"unlocked"}
91 | - PORT=9000
92 | - OWN_URL=node4
93 | - GETH_PORT=21000
94 | - GETH_RPC_PORT=22000
95 | - OTHER_NODES=http://node1:9000/
96 | - 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"]
97 |
98 | networks:
99 | quorum_net:
100 | driver: bridge
101 | ipam:
102 | driver: default
103 | config:
104 | - subnet: 10.5.0.0/24
--------------------------------------------------------------------------------
/docker/quorum-crux/docker-compose-local.yaml:
--------------------------------------------------------------------------------
1 | # build the docker container locally
2 | version: "3.4"
3 | services:
4 |
5 | node1: &quorum_crux_node
6 | # Pull image down from Docker Hub
7 | # image: blkio10/quorum-crux:v1.0.0
8 | # Uncomment the below, and comment out the above line to build the Docker images yourself
9 | image: blk.io/quorum/quorum-crux
10 | build:
11 | context: .
12 | container_name: quorum1
13 | ports:
14 | - 22001:22000
15 | - 21001:21000
16 | - 9001:9000
17 | restart: always
18 | networks:
19 | quorum_net:
20 | ipv4_address: 10.5.0.11
21 | environment:
22 | - 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}
23 | - NODE_KEY=633b2ef3ed5306f05a532eb44eb84858aca470d4fde039c1c988088e48070e64
24 | - CRUX_PUB=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=
25 | - CRUX_PRIV={"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"}
26 | - PORT=9000
27 | - OWN_URL=node1
28 | - GETH_PORT=21000
29 | - GETH_RPC_PORT=22000
30 | - OTHER_NODES=http://node2:9000/
31 | - 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"]
32 |
33 | node2:
34 | <<: *quorum_crux_node
35 | container_name: quorum2
36 | ports:
37 | - 22002:22000
38 | - 21002:21000
39 | - 9002:9000
40 | networks:
41 | quorum_net:
42 | ipv4_address: 10.5.0.12
43 | environment:
44 | - 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}
45 | - NODE_KEY=d5b8dfced693dbb7bf858dce40e3b2a0373696ac88ccf5e1c0052e26b7d77e49
46 | - CRUX_PUB=QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=
47 | - CRUX_PRIV={"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"}
48 | - PORT=9000
49 | - OWN_URL=node2
50 | - GETH_PORT=21000
51 | - GETH_RPC_PORT=22000
52 | - OTHER_NODES=http://node3:9000/
53 | - 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"]
54 |
55 | node3:
56 | <<: *quorum_crux_node
57 | container_name: quorum3
58 | ports:
59 | - 22003:22000
60 | - 9003:9000
61 | - 21003:21000
62 | networks:
63 | quorum_net:
64 | ipv4_address: 10.5.0.13
65 | environment:
66 | - 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}
67 | - NODE_KEY=45d87820ad7792f86592a8e3494c3e78b4755a480cfad1799d6c3f28c3f0d87c
68 | - CRUX_PUB=1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg=
69 | - CRUX_PRIV={"data":{"bytes":"tMxUVR8bX7aq/TbpVHc2QV3SN2iUuExBwefAuFsO0Lg="},"type":"unlocked"}
70 | - PORT=9000
71 | - OWN_URL=node3
72 | - GETH_PORT=21000
73 | - GETH_RPC_PORT=22000
74 | - OTHER_NODES=http://node4:9000/
75 | - 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"]
76 |
77 | node4:
78 | <<: *quorum_crux_node
79 | container_name: quorum4
80 | ports:
81 | - 22004:22000
82 | - 9004:9000
83 | - 21004:21000
84 | networks:
85 | quorum_net:
86 | ipv4_address: 10.5.0.14
87 | environment:
88 | - 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}
89 | - NODE_KEY=daa89d4ae250d24b33847343d0cc0116c48331b81e28514522bb7f77f2be5676
90 | - CRUX_PUB=oNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8=
91 | - CRUX_PRIV={"data":{"bytes":"grQjd3dBp4qFs8/5Jdq7xjz++aUx/LXAqISFyPWaCRw="},"type":"unlocked"}
92 | - PORT=9000
93 | - OWN_URL=node4
94 | - GETH_PORT=21000
95 | - GETH_RPC_PORT=22000
96 | - OTHER_NODES=http://node1:9000/
97 | - 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"]
98 |
99 | networks:
100 | quorum_net:
101 | driver: bridge
102 | ipam:
103 | driver: default
104 | config:
105 | - subnet: 10.5.0.0/24
106 |
--------------------------------------------------------------------------------
/Gopkg.lock:
--------------------------------------------------------------------------------
1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
2 |
3 |
4 | [[projects]]
5 | digest = "1:86840754a6a45d993a441d23094f501eb0cb0b12fe71f4c6cab2f3a826cb8725"
6 | name = "github.com/blk-io/chimera-api"
7 | packages = ["chimera"]
8 | pruneopts = "T"
9 | revision = "ebd4db90873296427420c2fe2acec18c127b401d"
10 |
11 | [[projects]]
12 | digest = "1:7fc160b460a6fc506b37fcca68332464c3f2cd57b6e3f111f26c5bbfd2d5518e"
13 | name = "github.com/fsnotify/fsnotify"
14 | packages = ["."]
15 | pruneopts = "T"
16 | revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
17 | version = "v1.4.7"
18 |
19 | [[projects]]
20 | digest = "1:832e17df5ff8bbe0e0693d2fb46c5e53f96c662ee804049ce3ab6557df74e3ab"
21 | name = "github.com/golang/protobuf"
22 | packages = [
23 | "jsonpb",
24 | "proto",
25 | "protoc-gen-go/descriptor",
26 | "ptypes",
27 | "ptypes/any",
28 | "ptypes/duration",
29 | "ptypes/struct",
30 | "ptypes/timestamp",
31 | ]
32 | pruneopts = "T"
33 | revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
34 | version = "v1.1.0"
35 |
36 | [[projects]]
37 | branch = "master"
38 | digest = "1:cf5bb7d7c59d8313289e5b756e24462cacd958d1e6db3bdbe1c800c677ad0f94"
39 | name = "github.com/golang/snappy"
40 | packages = ["."]
41 | pruneopts = "T"
42 | revision = "553a641470496b2327abcac10b36396bd98e45c9"
43 |
44 | [[projects]]
45 | digest = "1:69cd81163a00bb8405194d47b8be19283744779b6104f2d6b3735e2a01cdb6fa"
46 | name = "github.com/grpc-ecosystem/grpc-gateway"
47 | packages = [
48 | "runtime",
49 | "runtime/internal",
50 | "utilities",
51 | ]
52 | pruneopts = "T"
53 | revision = "92583770e3f01b09a0d3e9bdf64321d8bebd48f2"
54 | version = "v1.4.1"
55 |
56 | [[projects]]
57 | branch = "master"
58 | digest = "1:17a030e647213ad422df723c8ee8902328040dd74b7fc68cb016784baaf95657"
59 | name = "github.com/hashicorp/hcl"
60 | packages = [
61 | ".",
62 | "hcl/ast",
63 | "hcl/parser",
64 | "hcl/printer",
65 | "hcl/scanner",
66 | "hcl/strconv",
67 | "hcl/token",
68 | "json/parser",
69 | "json/scanner",
70 | "json/token",
71 | ]
72 | pruneopts = "T"
73 | revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
74 |
75 | [[projects]]
76 | branch = "master"
77 | digest = "1:e29757afd23af4c0c109594ccf0daaf8dd8e325f56ad38febbc4d13adfde0614"
78 | name = "github.com/jsimonetti/berkeleydb"
79 | packages = ["."]
80 | pruneopts = "T"
81 | revision = "5cde5eaaf78c6510c5f64f5347244806a06ba87b"
82 |
83 | [[projects]]
84 | digest = "1:599ec2ed1b0ab8e5b2b6d6d849c47cbbf5733d7988aee541fd1e5befe69b6095"
85 | name = "github.com/kevinburke/nacl"
86 | packages = [
87 | ".",
88 | "box",
89 | "onetimeauth",
90 | "randombytes",
91 | "scalarmult",
92 | "secretbox",
93 | ]
94 | pruneopts = "T"
95 | revision = "247b7cfd826641547b7ee8b89c785901c4e94b0b"
96 | version = "0.5"
97 |
98 | [[projects]]
99 | digest = "1:32ce5f79bec2865f25ae331a39ad67e507c9a6ed0b0da298db2c29efa8fe366f"
100 | name = "github.com/magiconair/properties"
101 | packages = ["."]
102 | pruneopts = "T"
103 | revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6"
104 | version = "v1.7.6"
105 |
106 | [[projects]]
107 | branch = "master"
108 | digest = "1:2514da1e59c0a936d8c1e0fbf5592267a3c5893eb4555ce767bb54d149e9cf6e"
109 | name = "github.com/mitchellh/mapstructure"
110 | packages = ["."]
111 | pruneopts = "T"
112 | revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
113 |
114 | [[projects]]
115 | digest = "1:63fc640566e87c5a5f2f3646725f308cf1a6b8e30fdba2d49941d978c3cb9b4d"
116 | name = "github.com/pelletier/go-toml"
117 | packages = ["."]
118 | pruneopts = "T"
119 | revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8"
120 | version = "v1.1.0"
121 |
122 | [[projects]]
123 | digest = "1:047075087eff9bcda0806c636c479fae29b9ae55da320d3d961eb5d3203e2a75"
124 | name = "github.com/sirupsen/logrus"
125 | packages = ["."]
126 | pruneopts = "T"
127 | revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
128 | version = "v1.0.5"
129 |
130 | [[projects]]
131 | digest = "1:ed2a3679c070c90f83aacc19d114536db7c5b739980837fbdc86cbf937493400"
132 | name = "github.com/spf13/afero"
133 | packages = [
134 | ".",
135 | "mem",
136 | ]
137 | pruneopts = "T"
138 | revision = "63644898a8da0bc22138abf860edaf5277b6102e"
139 | version = "v1.1.0"
140 |
141 | [[projects]]
142 | digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
143 | name = "github.com/spf13/cast"
144 | packages = ["."]
145 | pruneopts = "T"
146 | revision = "8965335b8c7107321228e3e3702cab9832751bac"
147 | version = "v1.2.0"
148 |
149 | [[projects]]
150 | branch = "master"
151 | digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805"
152 | name = "github.com/spf13/jwalterweatherman"
153 | packages = ["."]
154 | pruneopts = "T"
155 | revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
156 |
157 | [[projects]]
158 | digest = "1:9798f8595f3bf57586a622e8e78b7b8d159ceb375b9485c7fbcd2fd686ac4c46"
159 | name = "github.com/spf13/pflag"
160 | packages = ["."]
161 | pruneopts = "T"
162 | revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
163 | version = "v1.0.0"
164 |
165 | [[projects]]
166 | digest = "1:fbfebb70b35ccd17f5a91ba29f3d9dfeea149a98b44780a14efade3526c09bb3"
167 | name = "github.com/spf13/viper"
168 | packages = ["."]
169 | pruneopts = "T"
170 | revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
171 | version = "v1.0.2"
172 |
173 | [[projects]]
174 | branch = "master"
175 | digest = "1:ef0753127ee10562925146777ed5f5f41431c0ccc8fa63da1cfae678cc7b8c74"
176 | name = "github.com/syndtr/goleveldb"
177 | packages = [
178 | "leveldb",
179 | "leveldb/cache",
180 | "leveldb/comparer",
181 | "leveldb/errors",
182 | "leveldb/filter",
183 | "leveldb/iterator",
184 | "leveldb/journal",
185 | "leveldb/memdb",
186 | "leveldb/opt",
187 | "leveldb/storage",
188 | "leveldb/table",
189 | "leveldb/util",
190 | ]
191 | pruneopts = "T"
192 | revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad"
193 |
194 | [[projects]]
195 | branch = "master"
196 | digest = "1:dbe2585d9a08433ff9d1951bab1df0bc8c1bbd50c9fb866f92b661d88beb5694"
197 | name = "golang.org/x/crypto"
198 | packages = [
199 | "curve25519",
200 | "poly1305",
201 | "salsa20/salsa",
202 | "sha3",
203 | "ssh/terminal",
204 | ]
205 | pruneopts = "T"
206 | revision = "beb2a9779c3b677077c41673505f150149fce895"
207 |
208 | [[projects]]
209 | branch = "master"
210 | digest = "1:1591a31d3ebd0f8d083ec588de4fb0b540809cf75edee9467c554cc17da0620a"
211 | name = "golang.org/x/net"
212 | packages = [
213 | "context",
214 | "http/httpguts",
215 | "http2",
216 | "http2/hpack",
217 | "idna",
218 | "internal/timeseries",
219 | "trace",
220 | ]
221 | pruneopts = "T"
222 | revision = "89e543239a64caf31d3a6865872ea120b41446df"
223 |
224 | [[projects]]
225 | branch = "master"
226 | digest = "1:0c0ea93b25f3ef36c9662a679ba5fddae56ade049701857d020e3e73ae707e4b"
227 | name = "golang.org/x/sys"
228 | packages = [
229 | "unix",
230 | "windows",
231 | ]
232 | pruneopts = "T"
233 | revision = "3b87a42e500a6dc65dae1a55d0b641295971163e"
234 |
235 | [[projects]]
236 | digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
237 | name = "golang.org/x/text"
238 | packages = [
239 | "collate",
240 | "collate/build",
241 | "internal/colltab",
242 | "internal/gen",
243 | "internal/tag",
244 | "internal/triegen",
245 | "internal/ucd",
246 | "language",
247 | "secure/bidirule",
248 | "transform",
249 | "unicode/bidi",
250 | "unicode/cldr",
251 | "unicode/norm",
252 | "unicode/rangetable",
253 | ]
254 | pruneopts = "T"
255 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
256 | version = "v0.3.0"
257 |
258 | [[projects]]
259 | branch = "master"
260 | digest = "1:baedbfe72924071a9c7a4f8f12819d3b18a87743f645e5b638ba7a25865f00b1"
261 | name = "google.golang.org/genproto"
262 | packages = [
263 | "googleapis/api/annotations",
264 | "googleapis/rpc/status",
265 | ]
266 | pruneopts = "T"
267 | revision = "694d95ba50e67b2e363f3483057db5d4910c18f9"
268 |
269 | [[projects]]
270 | digest = "1:cb75df728d7afe7b7a7f546c19b8fd1c5fc801bdfea88fbd1a6a4a0af5072e3a"
271 | name = "google.golang.org/grpc"
272 | packages = [
273 | ".",
274 | "balancer",
275 | "balancer/base",
276 | "balancer/roundrobin",
277 | "channelz",
278 | "codes",
279 | "connectivity",
280 | "credentials",
281 | "encoding",
282 | "encoding/proto",
283 | "grpclb/grpc_lb_v1/messages",
284 | "grpclog",
285 | "internal",
286 | "keepalive",
287 | "metadata",
288 | "naming",
289 | "peer",
290 | "resolver",
291 | "resolver/dns",
292 | "resolver/passthrough",
293 | "stats",
294 | "status",
295 | "tap",
296 | "transport",
297 | ]
298 | pruneopts = "T"
299 | revision = "41344da2231b913fa3d983840a57a6b1b7b631a1"
300 | version = "v1.12.0"
301 |
302 | [[projects]]
303 | digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
304 | name = "gopkg.in/yaml.v2"
305 | packages = ["."]
306 | pruneopts = "T"
307 | revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
308 | version = "v2.2.1"
309 |
310 | [solve-meta]
311 | analyzer-name = "dep"
312 | analyzer-version = 1
313 | input-imports = [
314 | "github.com/blk-io/chimera-api/chimera",
315 | "github.com/grpc-ecosystem/grpc-gateway/runtime",
316 | "github.com/jsimonetti/berkeleydb",
317 | "github.com/kevinburke/nacl",
318 | "github.com/kevinburke/nacl/box",
319 | "github.com/kevinburke/nacl/secretbox",
320 | "github.com/sirupsen/logrus",
321 | "github.com/spf13/pflag",
322 | "github.com/spf13/viper",
323 | "github.com/syndtr/goleveldb/leveldb",
324 | "golang.org/x/crypto/sha3",
325 | "golang.org/x/net/context",
326 | "google.golang.org/grpc",
327 | "google.golang.org/grpc/credentials",
328 | ]
329 | solver-name = "gps-cdcl"
330 | solver-version = 1
331 |
--------------------------------------------------------------------------------
/config/config_testdata.conf:
--------------------------------------------------------------------------------
1 | #####
2 | ## Constellation configuration file example
3 | ## ----------------------------------------
4 | ## Every option listed here can also be specified on the command line, e.g.
5 | ## `constellation-node --url=http://www.foo.com --port 9001 ...`
6 | ## (lists are given using comma-separated strings)
7 | ## If both command line parameters and a configuration file are given, the
8 | ## command line options will take precedence.
9 | ##
10 | ## The only strictly necessary option is `port`, however it's recommended to
11 | ## set at least the following:
12 | ##
13 | ## --url The URL to advertise to other nodes (reachable by them)
14 | ## --port The local port to listen on
15 | ## --workdir The folder to put stuff in (default: .)
16 | ## --socket IPC socket to create for access to the Private API
17 | ## --othernodes "Boot nodes" to connect to to discover the network
18 | ## --publickeys Public keys hosted by this node
19 | ## --privatekeys Private keys hosted by this node (in corresponding order)
20 | ##
21 | ## Example usage:
22 | ##
23 | ## constellation-node --workdir=data --generatekeys=foo
24 | ## (To generate a keypair foo in the data directory)
25 | ##
26 | ## constellation-node --url=https://localhost:9000/ \
27 | ## --port=9000 \
28 | ## --workdir=data \
29 | ## --socket=constellation.ipc \
30 | ## --othernodes=https://localhost:9001/ \
31 | ## --publickeys=foo.pub \
32 | ## --privatekeys=foo.key
33 | ##
34 | ## constellation-node sample.conf
35 | ##
36 | ## constellation-node --port=9002 sample.conf
37 | ## (This overrides the port value given in sample.conf)
38 | ##
39 | ## Note on defaults: "Default:" below indicates the value that will be assumed
40 | ## if the option is not present either in the configuration file or as a command
41 | ## line parameter.
42 | ##
43 | ## Note about security: In the default configuration, Constellation will
44 | ## automatically generate TLS certificates and trust other nodes' certificates
45 | ## when they're first encountered (trust-on-first-use). See the documentation
46 | ## for tlsservertrust and tlsclienttrust below. To disable TLS entirely, e.g.
47 | ## when using Constellation in conjunction with a VPN like WireGuard, set tls to
48 | ## off.
49 | #####
50 |
51 | ## Externally accessible URL for this node's public API (this is what's
52 | ## advertised to other nodes on the network, and must be reachable by them.)
53 | url = "http://127.0.0.1:9001/"
54 |
55 | ## Port to listen on for the public API.
56 | port = 9001
57 |
58 | ## Directory in which to put and look for other files referenced here.
59 | ##
60 | ## Default: The current directory
61 | workdir = "data"
62 |
63 | ## Socket file to use for the private API / IPC. If this is commented out,
64 | ## the private API will not be accessible.
65 | ##
66 | ## Default: Not set
67 | socket = "constellation.ipc"
68 |
69 | ## Initial (not necessarily complete) list of other nodes in the network.
70 | ## Constellation will automatically connect to other nodes not in this list
71 | ## that are advertised by the nodes below, thus these can be considered the
72 | ## "boot nodes."
73 | ##
74 | ## Default: []
75 | othernodes = ["http://127.0.0.1:9000/"]
76 |
77 | ## The set of public keys this node will host.
78 | ##
79 | ## Default: []
80 | publickeys = ["foo.pub"]
81 |
82 | ## The corresponding set of private keys. These must correspond to the public
83 | ## keys listed above.
84 | ##
85 | ## Default: []
86 | privatekeys = ["foo.key"]
87 |
88 | ## Optional comma-separated list of paths to public keys to add as recipients
89 | ## for every transaction sent through this node, e.g. for backup purposes.
90 | ## These keys must be advertised by some Constellation node on the network, i.e.
91 | ## be in a node's publickeys/privatekeys lists.
92 | ##
93 | ## Default: []
94 | alwayssendto = []
95 |
96 | ## Optional file containing the passwords needed to unlock the given privatekeys
97 | ## (the file should contain one password per line -- add an empty line if any
98 | ## one key isn't locked.)
99 | ##
100 | ## Default: Not set
101 | # passwords = "passwords"
102 |
103 | ## Storage engine used to save payloads and related information. Options:
104 | ## - bdb:path (BerkeleyDB)
105 | ## - dir:path (Directory/file storage - can be used with e.g. FUSE-mounted
106 | ## file systems.)
107 | ## - leveldb:path (LevelDB - experimental)
108 | ## - memory (Contents are cleared when Constellation exits)
109 | ## - sqlite:path (SQLite - experimental)
110 | ##
111 | ## Default: "dir:storage"
112 | storage = "dir:storage"
113 |
114 | ## Verbosity level (each level includes all prior levels)
115 | ## - 0: Only fatal errors
116 | ## - 1: Warnings
117 | ## - 2: Informational messages
118 | ## - 3: Debug messages
119 | ##
120 | ## At the command line this can be specified using -v0, -v1, -v2, -v3, or
121 | ## -v (2) and -vv (3).
122 | ##
123 | ## Default: 1
124 | verbosity = 1
125 |
126 | ## Optional IP whitelist for the public API. If unspecified/empty,
127 | ## connections from all sources will be allowed (but the private API remains
128 | ## accessible only via the IPC socket above.) To allow connections from
129 | ## localhost when a whitelist is defined, e.g. when running multiple
130 | ## Constellation nodes on the same machine, add "127.0.0.1" and "::1" to
131 | ## this list.
132 | ##
133 | ## Default: Not set
134 | # ipwhitelist = ["10.0.0.1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"]
135 |
136 | ## TLS status. Options:
137 | ##
138 | ## - strict: All connections to and from this node must use TLS with mutual
139 | ## authentication. See the documentation for tlsservertrust and
140 | ## tlsclienttrust below.
141 | ## - off: Mutually authenticated TLS is not used for in- and outbound
142 | ## connections, although unauthenticated connections to HTTPS hosts are
143 | ## still possible. This should only be used if another transport security
144 | ## mechanism like WireGuard is in place.
145 | ##
146 | ## Default: "strict"
147 | tls = "strict"
148 |
149 | ## Path to a file containing the server's TLS certificate in Apache format.
150 | ## This is used to identify this node to other nodes in the network when they
151 | ## connect to the public API.
152 | ##
153 | ## This file will be auto-generated if it doesn't exist.
154 | ##
155 | ## Default: "tls-server-cert.pem"
156 | tlsservercert = "tls-server-cert.pem"
157 |
158 | ## List of files that constitute the CA trust chain for the server certificate.
159 | ## This can be empty for auto-generated/non-PKI-based certificates.
160 | ##
161 | ## Default: []
162 | tlsserverchain = []
163 |
164 | ## The private key file for the server TLS certificate.
165 | ##
166 | ## This file will be auto-generated if it doesn't exist.
167 | ##
168 | ## Default: "tls-server-key.pem"
169 | tlsserverkey = "tls-server-key.pem"
170 |
171 | ## TLS trust mode for the server. This decides who's allowed to connect to it.
172 | ## Options:
173 | ##
174 | ## - whitelist: Only nodes that have previously connected to this node and
175 | ## been added to the tlsknownclients file below will be allowed to connect.
176 | ## This mode will not add any new clients to the tlsknownclients file.
177 | ##
178 | ## - tofu: (Trust-on-first-use) Only the first node that connects identifying
179 | ## as a certain host will be allowed to connect as the same host in the
180 | ## future. Note that nodes identifying as other hosts will still be able
181 | ## to connect -- switch to whitelist after populating the tlsknownclients
182 | ## list to restrict access.
183 | ##
184 | ## - ca: Only nodes with a valid certificate and chain of trust to one of
185 | ## the system root certificates will be allowed to connect. The folder
186 | ## containing trusted root certificates can be overriden with the
187 | ## SYSTEM_CERTIFICATE_PATH environment variable.
188 | ##
189 | ## - ca-or-tofu: A combination of ca and tofu: If a certificate is valid,
190 | ## it is always allowed and added to the tlsknownclients list. If it is
191 | ## self-signed, it will be allowed only if it's the first certificate this
192 | ## node has seen for that host.
193 | ##
194 | ## - insecure-no-validation: Any client can connect, however they will still
195 | ## be added to the tlsknownclients file.
196 | ##
197 | ## Default: "tofu"
198 | tlsservertrust = "tofu"
199 |
200 | ## TLS known clients file for the server. This contains the fingerprints of
201 | ## public keys of other nodes that are allowed to connect to this one.
202 | ##
203 | ## Default: "tls-known-clients"
204 | tlsknownclients = "tls-known-clients"
205 |
206 | ## Path to a file containing the client's TLS certificate in Apache format.
207 | ## This is used to identify this node to other nodes in the network when it is
208 | ## connecting to their public APIs.
209 | ##
210 | ## This file will be auto-generated if it doesn't exist.
211 | ##
212 | ## Default: "tls-client-cert.pem"
213 | tlsclientcert = "tls-client-cert.pem"
214 |
215 | ## List of files that constitute the CA trust chain for the client certificate.
216 | ## This can be empty for auto-generated/non-PKI-based certificates.
217 | ##
218 | ## Default: []
219 | tlsclientchain = []
220 |
221 | ## The private key file for the client TLS certificate.
222 | ##
223 | ## This file will be auto-generated if it doesn't exist.
224 | ##
225 | ## Default: "tls-client-key.pem"
226 | tlsclientkey = "tls-client-key.pem"
227 |
228 | ## TLS trust mode for the client. This decides which servers it will connect to.
229 | ## Options:
230 | ##
231 | ## - whitelist: This node will only connect to servers it has previously seen
232 | ## and added to the tlsknownclients file below. This mode will not add
233 | ## any new servers to the tlsknownservers file.
234 | ##
235 | ## - tofu: (Trust-on-first-use) This node will only connect to the same
236 | ## server for any given host. (Similar to how OpenSSH works.)
237 | ##
238 | ## - ca: The node will only connect to servers with a valid certificate and
239 | ## chain of trust to one of the system root certificates. The folder
240 | ## containing trusted root certificates can be overriden with the
241 | ## SYSTEM_CERTIFICATE_PATH environment variable.
242 | ##
243 | ## - ca-or-tofu: A combination of ca and tofu: If a certificate is valid,
244 | ## it is always allowed and added to the tlsknownservers list. If it is
245 | ## self-signed, it will be allowed only if it's the first certificate this
246 | ## node has seen for that host.
247 | ##
248 | ## - insecure-no-validation: This node will connect to any server, regardless
249 | ## of certificate, however it will still be added to the tlsknownservers
250 | ## file.
251 | ##
252 | ## Default: "ca-or-tofu"
253 | tlsclienttrust = "ca-or-tofu"
254 |
255 | ## TLS known servers file for the client. This contains the fingerprints of
256 | ## public keys of other nodes that this node has encountered.
257 | ##
258 | ## Default: "tls-known-servers"
259 | tlsknownservers = "tls-known-servers"
--------------------------------------------------------------------------------
/api/internal.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/blk-io/chimera-api/chimera"
9 | "github.com/blk-io/crux/utils"
10 | "github.com/kevinburke/nacl"
11 | log "github.com/sirupsen/logrus"
12 | "golang.org/x/net/context"
13 | "google.golang.org/grpc"
14 | "io/ioutil"
15 | "math/rand"
16 | "net/http"
17 | "net/http/httputil"
18 | "net/url"
19 | "time"
20 | )
21 |
22 | // EncryptedPayload is the struct used for storing all data associated with an encrypted
23 | // transaction.
24 | type EncryptedPayload struct {
25 | Sender nacl.Key
26 | CipherText []byte
27 | Nonce nacl.Nonce
28 | RecipientBoxes [][]byte
29 | RecipientNonce nacl.Nonce
30 | }
31 |
32 | // PartyInfo is a struct that stores details of all enclave nodes (or parties) on the network.
33 | type PartyInfo struct {
34 | url string // URL identifying this node
35 | recipients map[[nacl.KeySize]byte]string // public key -> URL
36 | parties map[string]bool // Node (or party) URLs
37 | client utils.HttpClient
38 | grpc bool
39 | }
40 |
41 | // GetRecipient retrieves the URL associated with the provided recipient.
42 | func (s *PartyInfo) GetRecipient(key nacl.Key) (string, bool) {
43 | value, ok := s.recipients[*key]
44 | return value, ok
45 | }
46 |
47 | func (s *PartyInfo) GetAllValues() (string, map[[nacl.KeySize]byte]string, map[string]bool) {
48 | return s.url, s.recipients, s.parties
49 | }
50 |
51 | // InitPartyInfo initializes a new PartyInfo store.
52 | func InitPartyInfo(rawUrl string, otherNodes []string, client utils.HttpClient, grpc bool) PartyInfo {
53 | parties := make(map[string]bool)
54 | for _, node := range otherNodes {
55 | parties[node] = true
56 | }
57 |
58 | return PartyInfo{
59 | url: rawUrl,
60 | recipients: make(map[[nacl.KeySize]byte]string),
61 | parties: parties,
62 | client: client,
63 | grpc: grpc,
64 | }
65 | }
66 |
67 | // CreatePartyInfo creates a new PartyInfo struct.
68 | func CreatePartyInfo(
69 | url string,
70 | otherNodes []string,
71 | otherKeys []nacl.Key,
72 | client utils.HttpClient) PartyInfo {
73 |
74 | recipients := make(map[[nacl.KeySize]byte]string)
75 | parties := make(map[string]bool)
76 | for i, node := range otherNodes {
77 | parties[node] = true
78 | recipients[*otherKeys[i]] = node
79 | }
80 |
81 | return PartyInfo{
82 | url: url,
83 | recipients: recipients,
84 | parties: parties,
85 | client: client,
86 | }
87 | }
88 |
89 | // RegisterPublicKeys associates the provided public keys with this node.
90 | func (s *PartyInfo) RegisterPublicKeys(pubKeys []nacl.Key) {
91 | for _, pubKey := range pubKeys {
92 | s.recipients[*pubKey] = s.url
93 | }
94 | }
95 |
96 | func (s *PartyInfo) GetPartyInfoGrpc() {
97 | recipients := make(map[string][]byte)
98 | for key, url := range s.recipients {
99 | recipients[url] = key[:]
100 | }
101 | urls := make(map[string]bool)
102 | for k, v := range s.parties {
103 | urls[k] = v
104 | }
105 |
106 | for rawUrl := range urls {
107 | if rawUrl == s.url {
108 | continue
109 | }
110 | var completeUrl url.URL
111 | url, err := completeUrl.Parse(rawUrl)
112 | conn, err := grpc.Dial(url.Host, grpc.WithInsecure())
113 | if err != nil {
114 | log.Errorf("Connection to gRPC server failed with error %s", err)
115 | continue
116 | }
117 | defer conn.Close()
118 | cli := chimera.NewClientClient(conn)
119 | if cli == nil {
120 | log.Errorf("Client is not intialised")
121 | continue
122 | }
123 | party := chimera.PartyInfo{Url: rawUrl, Recipients: recipients, Parties: s.parties}
124 |
125 | partyInfoResp, err := cli.UpdatePartyInfo(context.Background(), &party)
126 | if err != nil {
127 | log.Errorf("Error in updating party info %s", err)
128 | continue
129 | } else {
130 | log.Printf("Connected to the other node %s", rawUrl)
131 | }
132 | err = s.updatePartyInfoGrpc(*partyInfoResp, s.url)
133 | if err != nil {
134 | log.Errorf("Error: %s", err)
135 | break
136 | }
137 | }
138 | }
139 |
140 | // GetPartyInfo requests PartyInfo data from all remote nodes this node is aware of. The data
141 | // provided in each response is applied to this node.
142 | func (s *PartyInfo) GetPartyInfo() {
143 | if s.grpc {
144 | s.GetPartyInfoGrpc()
145 | return
146 | }
147 | encodedPartyInfo := EncodePartyInfo(*s)
148 |
149 | // First copy our endpoints as we update this map in place
150 | urls := make(map[string]bool)
151 | for k, v := range s.parties {
152 | urls[k] = v
153 | }
154 |
155 | for rawUrl := range urls {
156 | if rawUrl == s.url {
157 | continue
158 | }
159 |
160 | endPoint, err := utils.BuildUrl(rawUrl, "/partyinfo")
161 |
162 | if err != nil {
163 | log.WithFields(log.Fields{"rawUrl": rawUrl, "endPoint": "/partyinfo"}).Errorf(
164 | "Invalid endpoint provided")
165 | }
166 |
167 | var req *http.Request
168 | encoded := s.getEncoded(encodedPartyInfo)
169 | req, err = http.NewRequest("POST", endPoint, bytes.NewBuffer(encoded))
170 |
171 | if err != nil {
172 | log.WithField("url", rawUrl).Errorf(
173 | "Error creating /partyinfo request, %v", err)
174 | break
175 | }
176 | req.Header.Set("Content-Type", "application/octet-stream")
177 |
178 | logRequest(req)
179 | resp, err := s.client.Do(req)
180 | if err != nil {
181 | log.WithField("url", rawUrl).Errorf(
182 | "Error sending /partyinfo request, %v", err)
183 | continue
184 | }
185 |
186 | if resp.StatusCode != http.StatusOK {
187 | log.WithField("url", rawUrl).Errorf(
188 | "Error sending /partyinfo request, non-200 status code: %v", resp)
189 | continue
190 | }
191 |
192 | err = s.updatePartyInfo(resp, rawUrl)
193 |
194 | if err != nil {
195 | break
196 | }
197 | }
198 | }
199 |
200 | func (s *PartyInfo) updatePartyInfoGrpc(partyInfoReq chimera.PartyInfoResponse, rawUrl string) error {
201 | pi, err := DecodePartyInfo(partyInfoReq.Payload)
202 | if err != nil {
203 | log.WithField("url", rawUrl).Errorf(
204 | "Unable to decode partyInfo response from host, %v", err)
205 | return err
206 | }
207 | s.UpdatePartyInfoGrpc(pi.url, pi.recipients, pi.parties)
208 | return nil
209 | }
210 |
211 | func (s *PartyInfo) updatePartyInfo(resp *http.Response, rawUrl string) error {
212 | var encoded []byte
213 | encoded, err := ioutil.ReadAll(resp.Body)
214 | resp.Body.Close()
215 | if err != nil {
216 | log.WithField("url", rawUrl).Errorf(
217 | "Unable to read partyInfo response from host, %v", err)
218 | return err
219 | }
220 | s.UpdatePartyInfo(encoded)
221 | return nil
222 | }
223 |
224 | func (s *PartyInfo) getEncoded(encodedPartyInfo []byte) []byte {
225 | if s.grpc {
226 | recipients := make(map[string][]byte)
227 | for key, url := range s.recipients {
228 | recipients[url] = key[:]
229 | }
230 | e, err := json.Marshal(UpdatePartyInfo{s.url, recipients, s.parties})
231 | if err != nil {
232 | log.Errorf("Marshalling failed %v", err)
233 | return nil
234 | }
235 | return e
236 | }
237 | return encodedPartyInfo[:]
238 | }
239 |
240 | func (s *PartyInfo) PollPartyInfo() {
241 | time.Sleep(time.Duration(rand.Intn(16)) * time.Second)
242 | s.GetPartyInfo()
243 |
244 | ticker := time.NewTicker(2 * time.Minute)
245 | quit := make(chan struct{})
246 | go func() {
247 | for {
248 | select {
249 | case <-ticker.C:
250 | s.GetPartyInfo()
251 | case <-quit:
252 | ticker.Stop()
253 | return
254 | }
255 | }
256 | }()
257 | }
258 |
259 | // UpdatePartyInfo updates the PartyInfo datastore with the provided encoded data.
260 | // This can happen from the /partyinfo server endpoint being hit, or by a response from us hitting
261 | // another nodes /partyinfo endpoint.
262 | // TODO: Control access via a channel for updates.
263 | func (s *PartyInfo) UpdatePartyInfo(encoded []byte) {
264 | log.Debugf("Updating party info payload: %s", hex.EncodeToString(encoded))
265 | pi, err := DecodePartyInfo(encoded)
266 |
267 | if err != nil {
268 | log.WithField("encoded", encoded).Errorf(
269 | "Unable to decode party info, error: %v", err)
270 | }
271 |
272 | for publicKey, url := range pi.recipients {
273 | // we should ignore messages about ourselves
274 | // in order to stop people masquerading as you, there
275 | // should be a digital signature associated with each
276 | // url -> node broadcast
277 | if url != s.url {
278 | s.recipients[publicKey] = url
279 | }
280 | }
281 |
282 | for url := range pi.parties {
283 | // we don't want to broadcast party info to ourselves
284 | s.parties[url] = true
285 | }
286 | }
287 |
288 | func (s *PartyInfo) UpdatePartyInfoGrpc(url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool) {
289 | for publicKey, url := range recipients {
290 | // we should ignore messages about ourselves
291 | // in order to stop people masquerading as you, there
292 | // should be a digital signature associated with each
293 | // url -> node broadcast
294 | if url != s.url {
295 | s.recipients[publicKey] = url
296 | }
297 | }
298 |
299 | for url := range parties {
300 | // we don't want to broadcast party info to ourselves
301 | s.parties[url] = true
302 | }
303 | }
304 |
305 | func PushGrpc(encoded []byte, path string, epl EncryptedPayload) error {
306 | var completeUrl url.URL
307 | url, err := completeUrl.Parse(path)
308 | conn, err := grpc.Dial(url.Host, grpc.WithInsecure())
309 | if err != nil {
310 | log.Fatalf("Connection to gRPC server failed with error %s", err)
311 | }
312 | defer conn.Close()
313 | cli := chimera.NewClientClient(conn)
314 | if cli == nil {
315 | log.Fatalf("Client is not intialised")
316 | }
317 |
318 | var sender [32]byte
319 | var nonce [32]byte
320 | var recipientNonce [32]byte
321 |
322 | copy(sender[:], (*epl.Sender)[:])
323 | copy(nonce[:], (*epl.Nonce)[:])
324 | copy(recipientNonce[:], (*epl.RecipientNonce)[:])
325 | encrypt := chimera.EncryptedPayload{
326 | Sender: sender[:],
327 | CipherText: epl.CipherText,
328 | Nonce: nonce[:],
329 | ReciepientNonce: recipientNonce[:],
330 | ReciepientBoxes: epl.RecipientBoxes,
331 | }
332 | pushPayload := chimera.PushPayload{Ep: &encrypt, Encoded: encoded}
333 | _, err = cli.Push(context.Background(), &pushPayload)
334 | if err != nil {
335 | log.Errorf("Push failed with %s", err)
336 | return err
337 | }
338 | return nil
339 | }
340 |
341 | // Push is responsible for propagating the encoded payload to the given remote node.
342 | func Push(encoded []byte, url string, client utils.HttpClient) (string, error) {
343 |
344 | endPoint, err := utils.BuildUrl(url, "/push")
345 | if err != nil {
346 | return "", err
347 | }
348 |
349 | var req *http.Request
350 | req, err = http.NewRequest("POST", endPoint, bytes.NewReader(encoded))
351 | if err != nil {
352 | return "", err
353 | }
354 | req.Header.Set("Content-Type", "application/octet-stream")
355 |
356 | logRequest(req)
357 | resp, err := client.Do(req)
358 | if err != nil {
359 | return "", err
360 | }
361 |
362 | if resp.StatusCode != http.StatusOK {
363 | return "", fmt.Errorf("non-200 status code received: %v", resp)
364 | }
365 |
366 | body, err := ioutil.ReadAll(resp.Body)
367 | resp.Body.Close()
368 |
369 | if err != nil {
370 | return "", err
371 | }
372 |
373 | return string(body), nil
374 | }
375 |
376 | func logRequest(r *http.Request) {
377 | if log.GetLevel() == log.DebugLevel {
378 | dump, err := httputil.DumpRequestOut(r, true)
379 | if err != nil {
380 | log.Fatal(err)
381 | }
382 |
383 | log.Debugf("%q", dump)
384 | }
385 | }
386 |
--------------------------------------------------------------------------------
/enclave/enclave_test.go:
--------------------------------------------------------------------------------
1 | package enclave
2 |
3 | import (
4 | "bytes"
5 | "github.com/blk-io/crux/api"
6 | "github.com/blk-io/crux/storage"
7 | "github.com/blk-io/crux/utils"
8 | "github.com/kevinburke/nacl"
9 | "io/ioutil"
10 | "net/http"
11 | "os"
12 | "path"
13 | "sync"
14 | "testing"
15 | "time"
16 | )
17 |
18 | var message = []byte("Test message")
19 |
20 | type MockClient struct {
21 | serviceMu sync.Mutex
22 | requests [][]byte
23 | }
24 |
25 | func (c *MockClient) Do(req *http.Request) (*http.Response, error) {
26 | body, err := ioutil.ReadAll(req.Body)
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | c.serviceMu.Lock()
32 | c.requests = append(c.requests, body)
33 | c.serviceMu.Unlock()
34 |
35 | respBody := ioutil.NopCloser(bytes.NewReader([]byte("")))
36 | return &http.Response{Body: respBody}, nil
37 | }
38 |
39 | func (c *MockClient) reqCount() int {
40 | c.serviceMu.Lock()
41 | defer c.serviceMu.Unlock()
42 | return len(c.requests)
43 | }
44 |
45 | func initEnclave(
46 | t *testing.T,
47 | dbPath string,
48 | pi api.PartyInfo,
49 | client utils.HttpClient) *SecureEnclave {
50 |
51 | db, err := storage.InitLevelDb(dbPath)
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 |
56 | return Init(
57 | db,
58 | []string{"testdata/key.pub"},
59 | []string{"testdata/key"},
60 | pi,
61 | client, false)
62 | }
63 |
64 | func initDefaultEnclave(t *testing.T,
65 | dbPath string) *SecureEnclave {
66 |
67 | var client utils.HttpClient
68 | client = &MockClient{}
69 | pi := api.InitPartyInfo(
70 | "http://localhost:8000",
71 | []string{"http://localhost:8001"}, client, false)
72 |
73 | return initEnclave(t, dbPath, pi, client)
74 | }
75 |
76 | func TestStoreAndRetrieve(t *testing.T) {
77 | dbPath, err := ioutil.TempDir("", "TestStoreAndRetrieve")
78 |
79 | if err != nil {
80 | t.Fatal(err)
81 | } else {
82 | defer os.RemoveAll(dbPath)
83 | }
84 |
85 | mockClient := &MockClient{requests: [][]byte{}}
86 | var client utils.HttpClient
87 | client = mockClient
88 |
89 | pubKeys, err := loadPubKeys([]string{"testdata/rcpt1.pub"})
90 | if err != nil {
91 | t.Fatal(err)
92 | }
93 | rcpt1 := pubKeys[0]
94 |
95 | pi := api.CreatePartyInfo(
96 | "http://localhost:8000",
97 | []string{"http://localhost:8001"},
98 | []nacl.Key{rcpt1},
99 | client)
100 |
101 | enc := initEnclave(t, dbPath, pi, client)
102 |
103 | var digest []byte
104 | digest, err = enc.Store(&message, []byte{}, [][]byte{(*rcpt1)[:]})
105 | if err != nil {
106 | t.Fatal(err)
107 | }
108 |
109 | var returned []byte
110 | returned, err = enc.Retrieve(&digest, nil)
111 |
112 | if !bytes.Equal(message, returned) {
113 | t.Errorf(
114 | "Retrieved message is not the same as original:\n"+
115 | "Original: %v\nRetrieved: %v",
116 | message, returned)
117 | }
118 |
119 | // We verify payload propagation too
120 | if mockClient.reqCount() != 1 {
121 | t.Errorf("Only one request should have been captured, actual: %d\n",
122 | len(mockClient.requests))
123 | }
124 |
125 | propagatedPl := mockClient.requests[0]
126 | epl, recipients := api.DecodePayloadWithRecipients(propagatedPl)
127 |
128 | if len(recipients) != 0 {
129 | t.Errorf("Recipients should be empty in data sent to other nodes, actual size: %d\n",
130 | len(recipients))
131 | }
132 |
133 | if len(epl.RecipientBoxes) != 1 {
134 | t.Errorf("There should only be one recipient box present, actual %d\n",
135 | len(epl.RecipientBoxes))
136 | }
137 |
138 | // Then we simulate the propagation and retrieval by the client
139 | db, err := storage.InitLevelDb(dbPath + "2")
140 | if err != nil {
141 | t.Fatal(err)
142 | }
143 |
144 | enc2 := Init(
145 | db,
146 | []string{"testdata/rcpt1.pub"},
147 | []string{"testdata/rcpt1"},
148 | pi,
149 | client, false)
150 |
151 | var digest2 []byte
152 | digest2, err = enc2.StorePayload(propagatedPl)
153 |
154 | if !bytes.Equal(digest, digest2) {
155 | t.Errorf("Local and propgated digests should be equal, local: %v, propagated: %v\n",
156 | digest, digest2)
157 | }
158 |
159 | var returned2 []byte
160 | to := (*rcpt1)[:]
161 | returned2, err = enc2.Retrieve(&digest2, &to)
162 |
163 | if !bytes.Equal(message, returned2) {
164 | t.Errorf(
165 | "Retrieved message is not the same as original:\n"+
166 | "Original: %v\nRetrieved: %v",
167 | message, returned)
168 | }
169 | }
170 |
171 | func TestStoreAndRetrieveSelf(t *testing.T) {
172 | dbPath, err := ioutil.TempDir("", "TestStoreAndRetrieveSelf")
173 |
174 | if err != nil {
175 | t.Fatal(err)
176 | } else {
177 | defer os.RemoveAll(dbPath)
178 | }
179 |
180 | enc := initDefaultEnclave(t, dbPath)
181 |
182 | digest, err := enc.Store(&message, []byte{}, [][]byte{})
183 | if err != nil {
184 | t.Fatal(err)
185 | }
186 |
187 | var returned []byte
188 | returned, err = enc.Retrieve(&digest, nil)
189 |
190 | if !bytes.Equal(message, returned) {
191 | t.Errorf(
192 | "Retrieved message is not the same as original:\n"+
193 | "Original: %v\nRetrieved: %v",
194 | message, returned)
195 | }
196 | }
197 |
198 | func TestStoreNotAuthorised(t *testing.T) {
199 | dbPath, err := ioutil.TempDir("", "TestStoreNotAuthorised")
200 |
201 | if err != nil {
202 | t.Fatal(err)
203 | } else {
204 | defer os.RemoveAll(dbPath)
205 | }
206 |
207 | enc := initDefaultEnclave(t, dbPath)
208 |
209 | pubKeys, err := loadPubKeys([]string{"testdata/rcpt1.pub"})
210 | if err != nil {
211 | t.Fatal(err)
212 | }
213 | rcpt1 := pubKeys[0]
214 |
215 | _, err = enc.Store(&message, (*rcpt1)[:], [][]byte{(*rcpt1)[:]})
216 | if err == nil {
217 | t.Error("SecureEnclave is not authorised to store messages")
218 | }
219 | }
220 |
221 | func TestRetrieveInvalid(t *testing.T) {
222 | dbPath, err := ioutil.TempDir("", "TestRetrieveInvalid")
223 |
224 | if err != nil {
225 | t.Fatal(err)
226 | } else {
227 | defer os.RemoveAll(dbPath)
228 | }
229 |
230 | enc := initDefaultEnclave(t, dbPath)
231 |
232 | digest := []byte("invalid")
233 | _, err = enc.Retrieve(&digest, nil)
234 | if err == nil {
235 | t.Error("Invalid digest requested")
236 | }
237 | }
238 |
239 | func TestRetrieveNotAuthorised(t *testing.T) {
240 | // If you know the source enclave of the message, you can retrieve passing in any value you
241 | // want in the to field. This may not be appropriate.
242 | // TODO: Confirm if we want to do this
243 | dbPath, err := ioutil.TempDir("", "TestRetrieveNotAuthorised")
244 |
245 | if err != nil {
246 | t.Fatal(err)
247 | } else {
248 | defer os.RemoveAll(dbPath)
249 | }
250 |
251 | enc := initDefaultEnclave(t, dbPath)
252 |
253 | pubKeys, err := loadPubKeys([]string{"testdata/rcpt1.pub", "testdata/rcpt2.pub"})
254 | if err != nil {
255 | t.Fatal(err)
256 | }
257 | rcpt1 := pubKeys[0]
258 | rcpt2 := pubKeys[1]
259 |
260 | var digest []byte
261 | digest, err = enc.Store(&message, []byte{}, [][]byte{(*rcpt1)[:]})
262 | if err != nil {
263 | t.Fatal(err)
264 | }
265 |
266 | var returned []byte
267 | to := (*rcpt2)[:]
268 | // we may want this to fail, as it won't work if the message didn't originate with us
269 | returned, err = enc.Retrieve(&digest, &to)
270 |
271 | if !bytes.Equal(message, returned) {
272 | t.Errorf(
273 | "Retrieved message is not the same as original:\n"+
274 | "Original: %v\nRetrieved: %v",
275 | message, returned)
276 | }
277 | }
278 |
279 | func TestDelete(t *testing.T) {
280 | dbPath, err := ioutil.TempDir("", "TestDelete")
281 |
282 | if err != nil {
283 | t.Fatal(err)
284 | } else {
285 | defer os.RemoveAll(dbPath)
286 | }
287 |
288 | enc := initDefaultEnclave(t, dbPath)
289 |
290 | digest, err := enc.Store(&message, []byte{}, [][]byte{})
291 | if err != nil {
292 | t.Fatal(err)
293 | }
294 |
295 | var returned []byte
296 | returned, err = enc.Retrieve(&digest, nil)
297 |
298 | if !bytes.Equal(message, returned) {
299 | t.Errorf(
300 | "Retrieved message is not the same as original:\n"+
301 | "Original: %v\nRetrieved: %v",
302 | message, returned)
303 | }
304 |
305 | err = enc.Delete(&digest)
306 | if err != nil {
307 | t.Errorf("Unable to delete payload for key: %v\n", &digest)
308 | }
309 |
310 | _, err = enc.Retrieve(&digest, nil)
311 | if err == nil {
312 | t.Errorf("No error returned requesting invalid payload")
313 | }
314 | }
315 |
316 | func TestRetrieveFor(t *testing.T) {
317 | dbPath, err := ioutil.TempDir("", "TestRetrieveFor")
318 |
319 | if err != nil {
320 | t.Fatal(err)
321 | } else {
322 | defer os.RemoveAll(dbPath)
323 | }
324 |
325 | enc := initDefaultEnclave(t, dbPath)
326 |
327 | pubKeys, err := loadPubKeys([]string{"testdata/rcpt1.pub"})
328 | if err != nil {
329 | t.Fatal(err)
330 | }
331 | rcpt1 := (*pubKeys[0])[:]
332 |
333 | digest, err := enc.Store(&message, []byte{}, [][]byte{rcpt1})
334 | if err != nil {
335 | t.Fatal(err)
336 | }
337 |
338 | var returned *[]byte
339 | returned, err = enc.RetrieveFor(&digest, &rcpt1)
340 |
341 | epl := api.DecodePayload(*returned)
342 |
343 | if len(epl.RecipientBoxes) != 1 {
344 | t.Errorf("Retrieved record does not contain a single box, total: %d",
345 | len(epl.RecipientBoxes))
346 | }
347 | }
348 |
349 | func TestRetrieveForInvalid(t *testing.T) {
350 | dbPath, err := ioutil.TempDir("", "TestRetrieveFor")
351 |
352 | if err != nil {
353 | t.Fatal(err)
354 | } else {
355 | defer os.RemoveAll(dbPath)
356 | }
357 |
358 | enc := initDefaultEnclave(t, dbPath)
359 |
360 | var pubKeys []nacl.Key
361 | pubKeys, err = loadPubKeys([]string{"testdata/rcpt1.pub"})
362 | if err != nil {
363 | t.Fatal(err)
364 | }
365 | rcpt1 := (*pubKeys[0])[:]
366 |
367 | digest := []byte("Invalid")
368 | _, err = enc.RetrieveFor(&digest, &rcpt1)
369 |
370 | if err == nil {
371 | t.Error("No error returned requesting invalid payload")
372 | }
373 | }
374 |
375 | func TestRetrieveAllFor(t *testing.T) {
376 | dbPath, err := ioutil.TempDir("", "TestRetrieveAllFor")
377 |
378 | if err != nil {
379 | t.Fatal(err)
380 | } else {
381 | defer os.RemoveAll(dbPath)
382 | }
383 |
384 | mockClient := &MockClient{requests: [][]byte{}}
385 | var client utils.HttpClient
386 | client = mockClient
387 |
388 | pubKeys, err := loadPubKeys([]string{"testdata/rcpt1.pub"})
389 | if err != nil {
390 | t.Fatal(err)
391 | }
392 | rcpt1 := pubKeys[0]
393 |
394 | pi := api.CreatePartyInfo(
395 | "http://localhost:8000",
396 | []string{"http://localhost:8001"},
397 | []nacl.Key{rcpt1},
398 | client)
399 |
400 | enc := initEnclave(t, dbPath, pi, client)
401 |
402 | _, err = enc.Store(&message, []byte{}, [][]byte{(*rcpt1)[:]})
403 | if err != nil {
404 | t.Fatal(err)
405 | }
406 |
407 | message2 := []byte("Another message")
408 | _, err = enc.Store(&message2, []byte{}, [][]byte{(*rcpt1)[:]})
409 | if err != nil {
410 | t.Fatal(err)
411 | }
412 |
413 | rcpt1Key := (*rcpt1)[:]
414 | err = enc.RetrieveAllFor(&rcpt1Key)
415 | if err != nil {
416 | t.Fatal(err)
417 | }
418 |
419 | // we need to wait for the replay go-routines to complete
420 | time.Sleep(1 * time.Millisecond)
421 | if mockClient.reqCount() != 4 {
422 | t.Errorf("Four requests should have been captured, actual: %d\n",
423 | len(mockClient.requests))
424 | }
425 | }
426 |
427 | func TestDoKeyGeneration(t *testing.T) {
428 | dbPath, err := ioutil.TempDir("", "TestDoKeyGeneration")
429 |
430 | if err != nil {
431 | t.Fatal(err)
432 | } else {
433 | defer os.RemoveAll(dbPath)
434 | }
435 |
436 | keyFiles := path.Join(dbPath, "testKey")
437 | err = DoKeyGeneration(keyFiles)
438 |
439 | if err != nil {
440 | t.Fatal(err)
441 | }
442 |
443 | _, err = loadPubKeys([]string{keyFiles + ".pub"})
444 | if err != nil {
445 | t.Fatal(err)
446 | }
447 |
448 | _, err = loadPrivKeys([]string{keyFiles + ".key"})
449 | if err != nil {
450 | t.Fatal(err)
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Enterprise Ethereum
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/docs/read-tx.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | // Package server contains the core server components.
2 | package server
3 |
4 | import (
5 | "encoding/base64"
6 | "encoding/hex"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/blk-io/crux/api"
10 | "github.com/blk-io/crux/utils"
11 | "github.com/kevinburke/nacl"
12 | log "github.com/sirupsen/logrus"
13 | "io/ioutil"
14 | "net/http"
15 | "net/http/httputil"
16 | "net/textproto"
17 | "os"
18 | "strconv"
19 | )
20 |
21 | // Enclave is the interface used by the transaction enclaves.
22 | type Enclave interface {
23 | Store(message *[]byte, sender []byte, recipients [][]byte) ([]byte, error)
24 | StorePayloadGrpc(epl api.EncryptedPayload, encoded []byte) ([]byte, error)
25 | StorePayload(encoded []byte) ([]byte, error)
26 | Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error)
27 | RetrieveDefault(digestHash *[]byte) ([]byte, error)
28 | RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (*[]byte, error)
29 | RetrieveAllFor(reqRecipient *[]byte) error
30 | Delete(digestHash *[]byte) error
31 | UpdatePartyInfo(encoded []byte)
32 | UpdatePartyInfoGrpc(url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool)
33 | GetEncodedPartyInfo() []byte
34 | GetEncodedPartyInfoGrpc() []byte
35 | GetPartyInfo() (url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool)
36 | }
37 |
38 | // TransactionManager is responsible for handling all transaction requests.
39 | type TransactionManager struct {
40 | Enclave Enclave
41 | }
42 |
43 | const upCheckResponse = "I'm up!"
44 | const apiVersion = "0.3.2"
45 |
46 | const version = "/version"
47 | const upCheck = "/upcheck"
48 | const push = "/push"
49 | const resend = "/resend"
50 | const partyInfo = "/partyinfo"
51 | const send = "/send"
52 | const sendRaw = "/sendraw"
53 | const receive = "/receive"
54 | const receiveRaw = "/receiveraw"
55 | const delete = "/delete"
56 |
57 | const hFrom = "c11n-from"
58 | const hTo = "c11n-to"
59 | const hKey = "c11n-key"
60 |
61 | func requestLogger(handler http.Handler) http.Handler {
62 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63 | if log.GetLevel() == log.DebugLevel {
64 | dump, err := httputil.DumpRequest(r, true)
65 | if err != nil {
66 | http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
67 | return
68 | }
69 |
70 | log.Debugf("%q", dump)
71 | }
72 |
73 | handler.ServeHTTP(w, r)
74 | })
75 | }
76 |
77 | // Init initializes a new TransactionManager instance.
78 | func Init(enc Enclave, networkInterface string, port int, ipcPath string, grpc bool, grpcJsonPort int, tls bool, certFile, keyFile string) (TransactionManager, error) {
79 | tm := TransactionManager{Enclave: enc}
80 | var err error
81 | if grpc == true {
82 | err = tm.startRpcServer(networkInterface, port, grpcJsonPort, ipcPath, tls, certFile, keyFile)
83 |
84 | } else {
85 | err = tm.startHttpserver(networkInterface, port, ipcPath, tls, certFile, keyFile)
86 | }
87 |
88 | return tm, err
89 | }
90 |
91 | func (tm *TransactionManager) startHttpserver(networkInterface string, port int, ipcPath string, tls bool, certFile, keyFile string) error {
92 | httpServer := http.NewServeMux()
93 | httpServer.HandleFunc(upCheck, tm.upcheck)
94 | httpServer.HandleFunc(version, tm.version)
95 | httpServer.HandleFunc(push, tm.push)
96 | httpServer.HandleFunc(resend, tm.resend)
97 | httpServer.HandleFunc(partyInfo, tm.partyInfo)
98 |
99 | serverUrl := networkInterface + ":" + strconv.Itoa(port)
100 | if tls {
101 | err := CheckCertFiles(certFile, keyFile)
102 | if err != nil {
103 | log.Fatal(err)
104 | }
105 | go func() {
106 | log.Fatal(http.ListenAndServeTLS(serverUrl, certFile, keyFile, requestLogger(httpServer)))
107 | }()
108 | log.Infof("HTTPS server is running at: %s", serverUrl)
109 | } else {
110 | go func() {
111 | log.Fatal(http.ListenAndServe(serverUrl, requestLogger(httpServer)))
112 | }()
113 | log.Infof("HTTP server is running at: %s", serverUrl)
114 | }
115 |
116 | // Restricted to IPC
117 | ipcServer := http.NewServeMux()
118 | ipcServer.HandleFunc(upCheck, tm.upcheck)
119 | ipcServer.HandleFunc(version, tm.version)
120 | ipcServer.HandleFunc(send, tm.send)
121 | ipcServer.HandleFunc(sendRaw, tm.sendRaw)
122 | ipcServer.HandleFunc(receive, tm.receive)
123 | ipcServer.HandleFunc(receiveRaw, tm.receiveRaw)
124 | ipcServer.HandleFunc(delete, tm.delete)
125 |
126 | ipc, err := utils.CreateIpcSocket(ipcPath)
127 | if err != nil {
128 | log.Fatalf("Failed to start IPC Server at %s", ipcPath)
129 | }
130 | go func() {
131 | log.Fatal(http.Serve(ipc, requestLogger(ipcServer)))
132 | }()
133 | log.Infof("IPC server is running at: %s", ipcPath)
134 |
135 | return err
136 | }
137 |
138 | func CheckCertFiles(certFile, keyFile string) error {
139 | if _, err := os.Stat(certFile); os.IsNotExist(err) {
140 | return err
141 | } else if _, err := os.Stat(keyFile); os.IsNotExist(err) {
142 | return err
143 | }
144 | return nil
145 | }
146 |
147 | func (s *TransactionManager) upcheck(w http.ResponseWriter, req *http.Request) {
148 | fmt.Fprint(w, upCheckResponse)
149 | }
150 |
151 | func (s *TransactionManager) version(w http.ResponseWriter, req *http.Request) {
152 | fmt.Fprint(w, apiVersion)
153 | }
154 |
155 | func (s *TransactionManager) send(w http.ResponseWriter, req *http.Request) {
156 | var sendReq api.SendRequest
157 | err := json.NewDecoder(req.Body).Decode(&sendReq)
158 | req.Body.Close()
159 | if err != nil {
160 | invalidBody(w, req, err)
161 | return
162 | }
163 |
164 | payload, err := base64.StdEncoding.DecodeString(sendReq.Payload)
165 | if err != nil {
166 | decodeError(w, req, "payload", sendReq.Payload, err)
167 | return
168 | }
169 |
170 | var key []byte
171 | key, err = s.processSend(w, req, sendReq.From, sendReq.To, &payload)
172 |
173 | if err != nil {
174 | log.Error(err)
175 | badRequest(w,
176 | fmt.Sprintf("Unable to store key: %s, with payload: %s, error: %s\n",
177 | key, payload, err))
178 | } else {
179 | encodedKey := base64.StdEncoding.EncodeToString(key)
180 | sendResp := api.SendResponse{Key: encodedKey}
181 | json.NewEncoder(w).Encode(sendResp)
182 | w.Header().Set("Content-Type", "application/json")
183 | }
184 | }
185 |
186 | func (s *TransactionManager) sendRaw(w http.ResponseWriter, req *http.Request) {
187 |
188 | from := req.Header.Get(hFrom)
189 |
190 | to, ok := req.Header[hTo]
191 | if !ok {
192 | to, ok = req.Header[textproto.CanonicalMIMEHeaderKey(hTo)]
193 | if !ok {
194 | to = []string{}
195 | }
196 | }
197 |
198 | payload, err := ioutil.ReadAll(req.Body)
199 | req.Body.Close()
200 | if err != nil {
201 | invalidBody(w, req, err)
202 | return
203 | }
204 |
205 | var key []byte
206 | key, err = s.processSend(w, req, from, to, &payload)
207 | if err != nil {
208 | internalServerError(w, "Unable to process request")
209 | return
210 | }
211 |
212 | // Uncomment the below for Quorum v2.0.1 or below
213 | // see https://github.com/jpmorganchase/quorum/commit/ee498061b5a74bf1f3290139a53840345fa038cb#diff-63fbbd6b2c0487b8cd4445e881822cdd
214 | //w.Write(key)
215 | // Then delete the below lines
216 | encodedKey := base64.StdEncoding.EncodeToString(key)
217 | fmt.Fprint(w, encodedKey)
218 | }
219 |
220 | func (s *TransactionManager) processSend(
221 | w http.ResponseWriter, req *http.Request,
222 | b64from string,
223 | b64recipients []string,
224 | payload *[]byte) ([]byte, error) {
225 |
226 | log.WithFields(log.Fields{
227 | "b64From": b64from,
228 | "b64Recipients": b64recipients,
229 | "payload": hex.EncodeToString(*payload)}).Debugf(
230 | "Processing send request")
231 |
232 | sender, err := base64.StdEncoding.DecodeString(b64from)
233 | if err != nil {
234 | decodeError(w, req, "sender", b64from, err)
235 | return nil, err
236 | }
237 |
238 | recipients := make([][]byte, len(b64recipients))
239 | for i, value := range b64recipients {
240 | recipient, err := base64.StdEncoding.DecodeString(value)
241 | if err != nil {
242 | decodeError(w, req, "recipient", value, err)
243 | return nil, err
244 | } else {
245 | recipients[i] = recipient
246 | }
247 | }
248 |
249 | return s.Enclave.Store(payload, sender, recipients)
250 | }
251 |
252 | func (s *TransactionManager) receive(w http.ResponseWriter, req *http.Request) {
253 | var receiveReq api.ReceiveRequest
254 | err := json.NewDecoder(req.Body).Decode(&receiveReq)
255 | req.Body.Close()
256 | if err != nil {
257 | invalidBody(w, req, err)
258 | return
259 | }
260 |
261 | var payload []byte
262 | payload, err = s.processReceive(w, req, receiveReq.Key, receiveReq.To)
263 |
264 | if err != nil {
265 | badRequest(w,
266 | fmt.Sprintf("Unable to retrieve payload for key: %s, error: %s\n",
267 | receiveReq.Key, err))
268 | } else {
269 | encodedPayload := base64.StdEncoding.EncodeToString(payload)
270 | sendResp := api.ReceiveResponse{Payload: encodedPayload}
271 | json.NewEncoder(w).Encode(sendResp)
272 | w.Header().Set("Content-Type", "application/json")
273 | }
274 | }
275 |
276 | func (s *TransactionManager) receiveRaw(w http.ResponseWriter, req *http.Request) {
277 |
278 | key := req.Header.Get(hKey)
279 | if key == "" {
280 | badRequest(w, "key not specified")
281 | return
282 | }
283 |
284 | to := req.Header.Get(hTo)
285 |
286 | payload, err := s.processReceive(w, req, key, to)
287 |
288 | if err != nil {
289 | badRequest(w, fmt.Sprintln(err))
290 | return
291 | }
292 |
293 | w.Write(payload)
294 | }
295 |
296 | func (s *TransactionManager) processReceive(
297 | w http.ResponseWriter, req *http.Request, b64Key, b64To string) ([]byte, error) {
298 |
299 | key, err := base64.StdEncoding.DecodeString(b64Key)
300 | if err != nil {
301 | return nil, fmt.Errorf("unable to decode key: %s", b64Key)
302 | }
303 |
304 | if b64To != "" {
305 | to, err := base64.StdEncoding.DecodeString(b64To)
306 | if err != nil {
307 | return nil, fmt.Errorf("unable to decode to: %s", b64Key)
308 | }
309 |
310 | return s.Enclave.Retrieve(&key, &to)
311 | } else {
312 | return s.Enclave.RetrieveDefault(&key)
313 | }
314 | }
315 |
316 | func (s *TransactionManager) delete(w http.ResponseWriter, req *http.Request) {
317 | var deleteReq api.DeleteRequest
318 | err := json.NewDecoder(req.Body).Decode(&deleteReq)
319 | req.Body.Close()
320 | if err != nil {
321 | invalidBody(w, req, err)
322 | return
323 | }
324 | key, err := base64.StdEncoding.DecodeString(deleteReq.Key)
325 | if err != nil {
326 | decodeError(w, req, "key", deleteReq.Key, err)
327 | } else {
328 | err = s.Enclave.Delete(&key)
329 | if err != nil {
330 | badRequest(w, fmt.Sprintf("Unable to delete key: %s, error: %s\n", key, err))
331 | }
332 | }
333 | }
334 |
335 | func (s *TransactionManager) push(w http.ResponseWriter, req *http.Request) {
336 | payload, err := ioutil.ReadAll(req.Body)
337 | req.Body.Close()
338 | if err != nil {
339 | internalServerError(w, fmt.Sprintf("Unable to read request body, error: %s\n", err))
340 | return
341 | }
342 |
343 | digestHash, err := s.Enclave.StorePayload(payload)
344 | if err != nil {
345 | badRequest(w, fmt.Sprintf("Unable to store payload, error: %s\n", err))
346 | return
347 | }
348 |
349 | w.Write(digestHash)
350 | }
351 |
352 | func (s *TransactionManager) resend(w http.ResponseWriter, req *http.Request) {
353 | var resendReq api.ResendRequest
354 | err := json.NewDecoder(req.Body).Decode(&resendReq)
355 | req.Body.Close()
356 | if err != nil {
357 | invalidBody(w, req, err)
358 | return
359 | }
360 |
361 | var publicKey []byte
362 | publicKey, err = base64.StdEncoding.DecodeString(resendReq.PublicKey)
363 | if err != nil {
364 | decodeError(w, req, "publicKey", resendReq.PublicKey, err)
365 | return
366 | }
367 |
368 | if resendReq.Type == "all" {
369 | err = s.Enclave.RetrieveAllFor(&publicKey)
370 | if err != nil {
371 | invalidBody(w, req, err)
372 | }
373 | } else if resendReq.Type == "individual" {
374 | var key []byte
375 | key, err = base64.StdEncoding.DecodeString(resendReq.Key)
376 | if err != nil {
377 | decodeError(w, req, "key", resendReq.Key, err)
378 | return
379 | }
380 |
381 | var encodedPl *[]byte
382 | encodedPl, err = s.Enclave.RetrieveFor(&key, &publicKey)
383 | if err != nil {
384 | invalidBody(w, req, err)
385 | return
386 | }
387 | w.Write(*encodedPl)
388 | }
389 | }
390 |
391 | func (s *TransactionManager) partyInfo(w http.ResponseWriter, req *http.Request) {
392 | payload, err := ioutil.ReadAll(req.Body)
393 | req.Body.Close()
394 | if err != nil {
395 | internalServerError(w, fmt.Sprintf("Unable to read request body, error: %s\n", err))
396 | return
397 | } else {
398 | s.Enclave.UpdatePartyInfo(payload)
399 | w.Write(s.Enclave.GetEncodedPartyInfo())
400 | }
401 | }
402 |
403 | func invalidBody(w http.ResponseWriter, req *http.Request, err error) {
404 | badRequest(w, fmt.Sprintf("Invalid request: %s, error: %s\n", req.URL, err))
405 | }
406 |
407 | func decodeError(w http.ResponseWriter, req *http.Request, name string, value string, err error) {
408 | badRequest(w,
409 | fmt.Sprintf("Invalid request: %s, unable to decode %s: %s, error: %s\n",
410 | req.URL, name, value, err))
411 | }
412 |
413 | func badRequest(w http.ResponseWriter, message string) {
414 | log.Error(message)
415 | w.WriteHeader(http.StatusBadRequest)
416 | fmt.Fprintf(w, message)
417 | }
418 |
419 | func internalServerError(w http.ResponseWriter, message string) {
420 | log.Error(message)
421 | w.WriteHeader(http.StatusInternalServerError)
422 | fmt.Fprintf(w, message)
423 | }
424 |
--------------------------------------------------------------------------------
/docs/new-tx.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/server_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/blk-io/chimera-api/chimera"
9 | "github.com/blk-io/crux/api"
10 | "github.com/blk-io/crux/enclave"
11 | "github.com/blk-io/crux/storage"
12 | "github.com/kevinburke/nacl"
13 | log "github.com/sirupsen/logrus"
14 | "golang.org/x/net/context"
15 | "google.golang.org/grpc"
16 | "io/ioutil"
17 | "net/http"
18 | "net/http/httptest"
19 | "path"
20 | "reflect"
21 | "testing"
22 | )
23 |
24 | const sender = "BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo="
25 | const receiver = "QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="
26 |
27 | var payload = []byte("payload")
28 | var encodedPayload = base64.StdEncoding.EncodeToString(payload)
29 |
30 | type MockEnclave struct{}
31 |
32 | func (s *MockEnclave) Store(message *[]byte, sender []byte, recipients [][]byte) ([]byte, error) {
33 | return *message, nil
34 | }
35 |
36 | func (s *MockEnclave) StorePayload(encoded []byte) ([]byte, error) {
37 | return encoded, nil
38 | }
39 | func (s *MockEnclave) StorePayloadGrpc(epl api.EncryptedPayload, encoded []byte) ([]byte, error) {
40 | return encoded, nil
41 | }
42 |
43 | func (s *MockEnclave) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) {
44 | return *digestHash, nil
45 | }
46 |
47 | func (s *MockEnclave) RetrieveDefault(digestHash *[]byte) ([]byte, error) {
48 | return *digestHash, nil
49 | }
50 |
51 | func (s *MockEnclave) RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (*[]byte, error) {
52 | return digestHash, nil
53 | }
54 |
55 | func (s *MockEnclave) RetrieveAllFor(reqRecipient *[]byte) error {
56 | return nil
57 | }
58 |
59 | func (s *MockEnclave) Delete(digestHash *[]byte) error {
60 | return nil
61 | }
62 |
63 | func (s *MockEnclave) UpdatePartyInfo(encoded []byte) {}
64 |
65 | func (s *MockEnclave) UpdatePartyInfoGrpc(string, map[[nacl.KeySize]byte]string, map[string]bool) {}
66 |
67 | func (s *MockEnclave) GetEncodedPartyInfo() []byte {
68 | return payload
69 | }
70 |
71 | func (s *MockEnclave) GetEncodedPartyInfoGrpc() []byte {
72 | return payload
73 | }
74 |
75 | func (s *MockEnclave) GetPartyInfo() (string, map[[nacl.KeySize]byte]string, map[string]bool) {
76 | return "", nil, nil
77 | }
78 |
79 | func TestUpcheck(t *testing.T) {
80 | tm := TransactionManager{}
81 | runSimpleGetRequest(t, upCheck, upCheckResponse, tm.upcheck)
82 | }
83 |
84 | func TestVersion(t *testing.T) {
85 | tm := TransactionManager{}
86 | runSimpleGetRequest(t, version, apiVersion, tm.version)
87 | }
88 |
89 | func runSimpleGetRequest(t *testing.T, url, response string, handlerFunc http.HandlerFunc) {
90 | req, err := http.NewRequest("GET", url, nil)
91 | if err != nil {
92 | t.Fatal(err)
93 | }
94 |
95 | rr := httptest.NewRecorder()
96 |
97 | handler := http.HandlerFunc(handlerFunc)
98 | handler.ServeHTTP(rr, req)
99 |
100 | if status := rr.Code; status != http.StatusOK {
101 | t.Errorf("handler returned wrong status code: got %v want %v\n",
102 | status, http.StatusOK)
103 | }
104 |
105 | if rr.Body.String() != response {
106 | t.Errorf("handler returned unexpected body: got %v want %v\n",
107 | rr.Body.String(), upCheckResponse)
108 | }
109 | }
110 |
111 | func TestSend(t *testing.T) {
112 | sendReqs := []api.SendRequest{
113 | {
114 | Payload: encodedPayload,
115 | From: sender,
116 | To: []string{receiver},
117 | },
118 | {
119 | Payload: encodedPayload,
120 | To: []string{},
121 | },
122 | {
123 | Payload: encodedPayload,
124 | },
125 | }
126 |
127 | response := api.SendResponse{}
128 | expected := api.SendResponse{Key: encodedPayload}
129 |
130 | tm := TransactionManager{Enclave: &MockEnclave{}}
131 |
132 | for _, sendReq := range sendReqs {
133 | runJsonHandlerTest(t, &sendReq, &response, &expected, send, tm.send)
134 | }
135 | }
136 |
137 | func TestGRPCSend(t *testing.T) {
138 | sendReqs := []chimera.SendRequest{
139 | {
140 | Payload: payload,
141 | From: sender,
142 | To: []string{receiver},
143 | },
144 | {
145 | Payload: payload,
146 | To: []string{},
147 | },
148 | {
149 | Payload: payload,
150 | },
151 | }
152 | expected := chimera.SendResponse{Key: payload}
153 |
154 | freePort, err := GetFreePort("localhost")
155 | if err != nil {
156 | log.Fatalf("failed to find a free port to start gRPC REST server: %s", err)
157 | }
158 | ipcPath := InitgRPCServer(t, true, freePort)
159 |
160 | var conn *grpc.ClientConn
161 | conn, err = grpc.Dial(fmt.Sprintf("passthrough:///unix://%s", ipcPath), grpc.WithInsecure())
162 | if err != nil {
163 | t.Fatalf("Connection to gRPC server failed with error %s", err)
164 | }
165 | defer conn.Close()
166 | c := chimera.NewClientClient(conn)
167 |
168 | for _, sendReq := range sendReqs {
169 | resp, err := c.Send(context.Background(), &sendReq)
170 | if err != nil {
171 | t.Fatalf("gRPC send failed with %s", err)
172 | }
173 | response := chimera.SendResponse{Key: resp.Key}
174 | if !reflect.DeepEqual(response, expected) {
175 | t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected)
176 | }
177 | }
178 | }
179 |
180 | func TestSendRaw(t *testing.T) {
181 | tm := TransactionManager{Enclave: &MockEnclave{}}
182 |
183 | headers := make(http.Header)
184 | headers[hFrom] = []string{sender}
185 | headers[hTo] = []string{receiver}
186 |
187 | runRawHandlerTest(t, headers, payload, []byte(encodedPayload), sendRaw, tm.sendRaw)
188 | // Uncomment the below for Quorum v2.0.1 or below
189 | //runRawHandlerTest(t, headers, payload, payload, sendRaw, tm.sendRaw)
190 | }
191 |
192 | func TestReceive(t *testing.T) {
193 |
194 | receiveReqs := []api.ReceiveRequest{
195 | {
196 | Key: encodedPayload,
197 | To: receiver,
198 | },
199 | }
200 |
201 | response := api.ReceiveResponse{}
202 | expected := api.ReceiveResponse{Payload: encodedPayload}
203 |
204 | tm := TransactionManager{Enclave: &MockEnclave{}}
205 |
206 | for _, receiveReq := range receiveReqs {
207 | runJsonHandlerTest(t, &receiveReq, &response, &expected, receive, tm.receive)
208 | }
209 | }
210 |
211 | func TestGRPCReceive(t *testing.T) {
212 | receiveReqs := []chimera.ReceiveRequest{
213 | {
214 | Key: payload,
215 | To: receiver,
216 | },
217 | }
218 | expected := chimera.ReceiveResponse{Payload: payload}
219 | freePort, err := GetFreePort("localhost")
220 | if err != nil {
221 | log.Fatalf("failed to find a free port to start gRPC REST server: %s", err)
222 | }
223 | ipcPath := InitgRPCServer(t, true, freePort)
224 | var conn *grpc.ClientConn
225 | conn, err = grpc.Dial(fmt.Sprintf("passthrough:///unix://%s", ipcPath), grpc.WithInsecure())
226 | if err != nil {
227 | t.Fatalf("Connection to gRPC server failed with error %s", err)
228 | }
229 | defer conn.Close()
230 | c := chimera.NewClientClient(conn)
231 |
232 | for _, receiveReq := range receiveReqs {
233 | resp, err := c.Receive(context.Background(), &receiveReq)
234 | if err != nil {
235 | t.Fatalf("gRPC receive failed with %s", err)
236 | }
237 | response := chimera.ReceiveResponse{Payload: resp.Payload}
238 | if !reflect.DeepEqual(response, expected) {
239 | t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected)
240 | }
241 | }
242 | }
243 |
244 | func TestReceivedRaw(t *testing.T) {
245 | tm := TransactionManager{Enclave: &MockEnclave{}}
246 |
247 | headers := make(http.Header)
248 | headers[hKey] = []string{encodedPayload}
249 | headers[hTo] = []string{receiver}
250 |
251 | runRawHandlerTest(t, headers, payload, payload, receiveRaw, tm.receiveRaw)
252 | }
253 |
254 | func TestNilKeyReceivedRaw(t *testing.T) {
255 | tm := TransactionManager{Enclave: &MockEnclave{}}
256 |
257 | headers := make(http.Header)
258 | headers[hKey] = []string{""}
259 | headers[hTo] = []string{receiver}
260 |
261 | runFailingRawHandlerTest(t, headers, payload, payload, receiveRaw, tm.receiveRaw)
262 | }
263 |
264 | func TestPush(t *testing.T) {
265 |
266 | epl := api.EncryptedPayload{
267 | Sender: nacl.NewKey(),
268 | CipherText: []byte(payload),
269 | Nonce: nacl.NewNonce(),
270 | RecipientBoxes: [][]byte{[]byte(payload)},
271 | RecipientNonce: nacl.NewNonce(),
272 | }
273 | var recipients [][]byte
274 |
275 | encoded := api.EncodePayloadWithRecipients(epl, recipients)
276 | req, err := http.NewRequest("POST", push, bytes.NewBuffer(encoded))
277 | if err != nil {
278 | t.Fatal(err)
279 | }
280 | rr := httptest.NewRecorder()
281 | tm := TransactionManager{Enclave: &MockEnclave{}}
282 |
283 | handler := http.HandlerFunc(tm.push)
284 | handler.ServeHTTP(rr, req)
285 |
286 | if status := rr.Code; status != http.StatusOK {
287 | t.Errorf("handler returned wrong status code: got %v want %v\n",
288 | status, http.StatusOK)
289 | }
290 |
291 | if !bytes.Equal(rr.Body.Bytes(), encoded) {
292 | t.Errorf("handler returned unexpected body: got %v wanted %v\n",
293 | rr.Body.String(), encoded)
294 | }
295 | }
296 |
297 | func TestDelete(t *testing.T) {
298 | sendReq := api.DeleteRequest{
299 | Key: encodedPayload,
300 | }
301 |
302 | var response, expected interface{}
303 |
304 | tm := TransactionManager{Enclave: &MockEnclave{}}
305 |
306 | runJsonHandlerTest(t, &sendReq, &response, &expected, delete, tm.delete)
307 | }
308 |
309 | func runJsonHandlerTest(
310 | t *testing.T,
311 | request, response, expected interface{},
312 | url string,
313 | handlerFunc http.HandlerFunc) {
314 |
315 | encoded, err := json.Marshal(request)
316 | if err != nil {
317 | t.Fatal(err)
318 | }
319 |
320 | var req *http.Request
321 | req, err = http.NewRequest("POST", url, bytes.NewBuffer(encoded))
322 | if err != nil {
323 | t.Fatal(err)
324 | }
325 |
326 | rr := httptest.NewRecorder()
327 |
328 | handler := http.HandlerFunc(handlerFunc)
329 | handler.ServeHTTP(rr, req)
330 |
331 | if status := rr.Code; status != http.StatusOK {
332 | t.Errorf("handler returned wrong status code: got %v want %v",
333 | status, http.StatusOK)
334 | }
335 |
336 | body := rr.Body.Bytes()
337 | if len(body) > 0 {
338 | err = json.Unmarshal(body, &response)
339 | if err != nil {
340 | t.Error(err)
341 | }
342 | }
343 |
344 | if !reflect.DeepEqual(response, expected) {
345 | t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected)
346 | }
347 | }
348 | func runFailingRawHandlerTest(
349 | t *testing.T,
350 | headers http.Header,
351 | payload, expected []byte,
352 | url string,
353 | handlerFunc http.HandlerFunc) {
354 |
355 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
356 | if err != nil {
357 | t.Fatal(err)
358 | }
359 |
360 | for k, v := range headers {
361 | req.Header.Set(k, v[0])
362 | }
363 |
364 | rr := httptest.NewRecorder()
365 |
366 | handler := http.HandlerFunc(handlerFunc)
367 | handler.ServeHTTP(rr, req)
368 |
369 | if status := rr.Code; status == http.StatusOK {
370 | t.Errorf("handler returned wrong status code: got %v want %v",
371 | status, http.StatusOK)
372 | }
373 | }
374 |
375 | func runRawHandlerTest(
376 | t *testing.T,
377 | headers http.Header,
378 | payload, expected []byte,
379 | url string,
380 | handlerFunc http.HandlerFunc) {
381 |
382 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
383 | if err != nil {
384 | t.Fatal(err)
385 | }
386 |
387 | for k, v := range headers {
388 | req.Header.Set(k, v[0])
389 | }
390 |
391 | rr := httptest.NewRecorder()
392 |
393 | handler := http.HandlerFunc(handlerFunc)
394 | handler.ServeHTTP(rr, req)
395 |
396 | if status := rr.Code; status != http.StatusOK {
397 | t.Errorf("handler returned wrong status code: got %v want %v",
398 | status, http.StatusOK)
399 | }
400 |
401 | response := rr.Body.Bytes()
402 |
403 | if !reflect.DeepEqual(response, expected) {
404 | t.Errorf("handler returned unexpected response: %v, expected: %v\n", response, expected)
405 | }
406 | }
407 |
408 | func TestResendIndividual(t *testing.T) {
409 | resendReq := api.ResendRequest{
410 | Type: "individual",
411 | PublicKey: sender,
412 | Key: encodedPayload,
413 | }
414 |
415 | body := runResendTest(t, resendReq)
416 |
417 | if !bytes.Equal(body, payload) {
418 | t.Errorf("handler returned unexpected body: got %v wanted %v\n",
419 | body, payload)
420 | }
421 | }
422 |
423 | func TestResendAll(t *testing.T) {
424 | resendReq := api.ResendRequest{
425 | Type: "all",
426 | PublicKey: sender,
427 | }
428 |
429 | body := runResendTest(t, resendReq)
430 |
431 | if len(body) != 0 {
432 | t.Errorf("handler returned unexpected body, it should be empty, instead received: %v\n",
433 | body)
434 | }
435 | }
436 |
437 | func runResendTest(t *testing.T, resendReq api.ResendRequest) []byte {
438 | encoded, err := json.Marshal(resendReq)
439 | if err != nil {
440 | t.Error(err)
441 | }
442 |
443 | var req *http.Request
444 | req, err = http.NewRequest("POST", push, bytes.NewBuffer(encoded))
445 | if err != nil {
446 | t.Fatal(err)
447 | }
448 |
449 | rr := httptest.NewRecorder()
450 | tm := TransactionManager{Enclave: &MockEnclave{}}
451 |
452 | handler := http.HandlerFunc(tm.resend)
453 | handler.ServeHTTP(rr, req)
454 |
455 | if status := rr.Code; status != http.StatusOK {
456 | t.Errorf("handler returned wrong status code: got %v want %v\n",
457 | status, http.StatusOK)
458 | }
459 |
460 | return rr.Body.Bytes()
461 | }
462 |
463 | func TestPartyInfo(t *testing.T) {
464 |
465 | partyInfos := []api.PartyInfo{
466 | api.CreatePartyInfo(
467 | "http://localhost:8000",
468 | []string{"http://localhost:8001"},
469 | []nacl.Key{nacl.NewKey()},
470 | http.DefaultClient),
471 |
472 | api.InitPartyInfo(
473 | "http://localhost:8000",
474 | []string{"http://localhost:8001"},
475 | http.DefaultClient, false),
476 | }
477 |
478 | for _, pi := range partyInfos {
479 | testRunPartyInfo(t, pi)
480 | }
481 | }
482 |
483 | func testRunPartyInfo(t *testing.T, pi api.PartyInfo) {
484 | encodedPartyInfo := api.EncodePartyInfo(pi)
485 | encoded, err := json.Marshal(api.PartyInfoResponse{Payload: encodedPartyInfo})
486 | if err != nil {
487 | t.Errorf("Marshalling failed %v", err)
488 | }
489 | req, err := http.NewRequest("POST", push, bytes.NewBuffer(encoded))
490 | if err != nil {
491 | t.Fatal(err)
492 | }
493 |
494 | rr := httptest.NewRecorder()
495 | tm := TransactionManager{Enclave: &MockEnclave{}}
496 |
497 | handler := http.HandlerFunc(tm.partyInfo)
498 | handler.ServeHTTP(rr, req)
499 |
500 | if status := rr.Code; status != http.StatusOK {
501 | t.Errorf("handler returned wrong status code: got %v want %v\n",
502 | status, http.StatusOK)
503 | }
504 |
505 | if !bytes.Equal(rr.Body.Bytes(), payload) {
506 | t.Errorf("handler returned unexpected body: got %v wanted %v\n",
507 | rr.Body.Bytes(), payload)
508 | }
509 | }
510 |
511 | func InitgRPCServer(t *testing.T, grpc bool, port int) string {
512 | ipcPath, err := ioutil.TempDir("", "TestInitIpc")
513 | tm, err := Init(&MockEnclave{}, "localhost", port, ipcPath, grpc, -1, false, "", "")
514 |
515 | if err != nil {
516 | t.Errorf("Error starting server: %v\n", err)
517 | }
518 | runSimpleGetRequest(t, upCheck, upCheckResponse, tm.upcheck)
519 | return ipcPath
520 | }
521 |
522 | func TestInit(t *testing.T) {
523 | dbPath, err := ioutil.TempDir("", "TestInit")
524 | if err != nil {
525 | t.Error(err)
526 | }
527 | db, err := storage.InitLevelDb(dbPath)
528 | if err != nil {
529 | t.Errorf("Error starting server: %v\n", err)
530 | }
531 | pubKeyFiles := []string{"key.pub"}
532 | privKeyFiles := []string{"key"}
533 |
534 | for i, keyFile := range privKeyFiles {
535 | privKeyFiles[i] = path.Join("../enclave/testdata", keyFile)
536 | }
537 |
538 | for i, keyFile := range pubKeyFiles {
539 | pubKeyFiles[i] = path.Join("../enclave/testdata", keyFile)
540 | }
541 |
542 | key := []nacl.Key{nacl.NewKey()}
543 |
544 | pi := api.CreatePartyInfo(
545 | "http://localhost:9000",
546 | []string{"http://localhost:9001"},
547 | key,
548 | http.DefaultClient)
549 |
550 | enc := enclave.Init(db, pubKeyFiles, privKeyFiles, pi, http.DefaultClient, false)
551 |
552 | ipcPath, err := ioutil.TempDir("", "TestInitIpc")
553 | if err != nil {
554 | t.Error(err)
555 | }
556 | certFile, keyFile := "../enclave/testdata/cert/server.crt", "../enclave/testdata/cert/server.key"
557 | tm, err := Init(enc, "localhost", 9001, ipcPath, false, -1, true, certFile, keyFile)
558 | if err != nil {
559 | t.Errorf("Error starting server: %v\n", err)
560 | }
561 | runSimpleGetRequest(t, upCheck, upCheckResponse, tm.upcheck)
562 | }
563 |
--------------------------------------------------------------------------------
/enclave/enclave.go:
--------------------------------------------------------------------------------
1 | // Package enclave provides enclaves for the secure storage and propagation of transactions.
2 | package enclave
3 |
4 | import (
5 | "bytes"
6 | "crypto/rand"
7 | "encoding/base64"
8 | "encoding/hex"
9 | "encoding/json"
10 | "errors"
11 | "fmt"
12 | "github.com/blk-io/crux/api"
13 | "github.com/blk-io/crux/storage"
14 | "github.com/blk-io/crux/utils"
15 | "github.com/kevinburke/nacl"
16 | "github.com/kevinburke/nacl/box"
17 | "github.com/kevinburke/nacl/secretbox"
18 | log "github.com/sirupsen/logrus"
19 | "io/ioutil"
20 | "path/filepath"
21 | "strings"
22 | )
23 |
24 | // SecureEnclave is the secure transaction enclave.
25 | type SecureEnclave struct {
26 | Db storage.DataStore // The underlying key-value datastore for encrypted transactions
27 | PubKeys []nacl.Key // Public keys associated with this enclave
28 | PrivKeys []nacl.Key // Private keys associated with this enclave
29 | selfPubKey nacl.Key // An ephemeral key used for transactions only intended for this enclave
30 | PartyInfo api.PartyInfo // Details of all other nodes (or parties) on the network
31 | keyCache map[nacl.Key]map[nacl.Key]nacl.Key // Maps sender -> recipient -> shared key
32 | client utils.HttpClient // The underlying HTTP client used to propagate requests
33 | grpc bool
34 | }
35 |
36 | // Init creates a new instance of the SecureEnclave.
37 | func Init(
38 | db storage.DataStore,
39 | pubKeyFiles, privKeyFiles []string,
40 | pi api.PartyInfo,
41 | client utils.HttpClient, grpc bool) *SecureEnclave {
42 |
43 | // Key format:
44 | // BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=
45 | pubKeys, err := loadPubKeys(pubKeyFiles)
46 | if err != nil {
47 | log.Fatalf("Unable to load public key files: %s, error: %v", pubKeyFiles, err)
48 | }
49 |
50 | // Key format:
51 | // {"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"}
52 | privKeys, err := loadPrivKeys(privKeyFiles)
53 | if err != nil {
54 | log.Fatalf("Unable to load private key files: %s, error: %v", privKeyFiles, err)
55 | }
56 |
57 | enc := SecureEnclave{
58 | Db: db,
59 | PubKeys: pubKeys,
60 | PrivKeys: privKeys,
61 | PartyInfo: pi,
62 | client: client,
63 | grpc: grpc,
64 | }
65 |
66 | // We use shared keys for encrypting data. The keys between a specific sender and recipient are
67 | // computed once for each unique pair.
68 | //
69 | // Encrypt scenarios:
70 | // The sender value must always be a public key that we have the corresponding private key for
71 | // privateFor: [] => encrypt with sharedKey [self-private, selfPub-public]
72 | // store in cache as (self-public, selfPub-public)
73 | // privateFor: [recipient1, ...] => encrypt with sharedKey1 [self-private, recipient1-public], ...
74 | // store in cache as (self-public, recipient1-public)
75 | // Decrypt scenarios:
76 | // epl, [] => The payload was pushed to us (we are recipient1), decrypt with sharedKey
77 | // [recipient1-private, sender-public]
78 | // lookup in cache as (recipient1-public, sender-public)
79 | // epl, [recipient1, ...,] => The payload originated with us (we are self), decrypt with
80 | // sharedKey [self-private, recipient1-public]
81 | // lookup in cache as (self-public, recipient1-public)
82 | //
83 | // Note that sharedKey(privA, pubB) produces the same key as sharedKey(pubA, privB), which is
84 | // why when sending to ones self we encrypt with sharedKey [self-private, selfPub-public], then
85 | // retrieve with sharedKey [self-private, selfPub-public]
86 | enc.keyCache = make(map[nacl.Key]map[nacl.Key]nacl.Key)
87 |
88 | enc.selfPubKey = nacl.NewKey()
89 |
90 | for _, pubKey := range enc.PubKeys {
91 | enc.keyCache[pubKey] = make(map[nacl.Key]nacl.Key)
92 |
93 | // We have a once off generated key which we use for storing payloads which are addressed
94 | // only to ourselves. We have to do this, as we cannot use box.Seal with a public and
95 | // private key-pair.
96 | //
97 | // We pre-compute these keys on startup.
98 | enc.resolveSharedKey(enc.PrivKeys[0], pubKey, enc.selfPubKey)
99 | }
100 |
101 | return &enc
102 | }
103 |
104 | // Store a payload submitted via an Ethereum node.
105 | // This function encrypts the payload, and distributes the encrypted payload to the other
106 | // specified recipients in the network.
107 | // The hash of the encrypted payload is returned to the sender.
108 | func (s *SecureEnclave) Store(
109 | message *[]byte, sender []byte, recipients [][]byte) ([]byte, error) {
110 |
111 | var err error
112 | var senderPubKey, senderPrivKey nacl.Key
113 |
114 | if len(sender) == 0 {
115 | // from address is either default or specified on communication
116 | senderPubKey = s.PubKeys[0]
117 | senderPrivKey = s.PrivKeys[0]
118 | } else {
119 | senderPubKey, err = utils.ToKey(sender)
120 | if err != nil {
121 | log.WithField("senderPubKey", sender).Errorf(
122 | "Unable to load sender public key, %v", err)
123 | return nil, err
124 | }
125 |
126 | senderPrivKey, err = s.resolvePrivateKey(senderPubKey)
127 | if err != nil {
128 | log.WithField("senderPubKey", sender).Errorf(
129 | "Unable to locate private key for sender public key, %v", err)
130 | return nil, err
131 | }
132 | }
133 |
134 | return s.store(message, senderPubKey, senderPrivKey, recipients)
135 | }
136 |
137 | func (s *SecureEnclave) store(
138 | message *[]byte,
139 | senderPubKey, senderPrivKey nacl.Key,
140 | recipients [][]byte) ([]byte, error) {
141 |
142 | var toSelf bool
143 | if len(recipients) == 0 {
144 | toSelf = true
145 | recipients = [][]byte{(*s.selfPubKey)[:]}
146 | } else {
147 | toSelf = false
148 | }
149 |
150 | epl, masterKey := createEncryptedPayload(message, senderPubKey, recipients)
151 |
152 | for i, recipient := range recipients {
153 |
154 | recipientKey, err := utils.ToKey(recipient)
155 | if err != nil {
156 | log.WithField("recipientKey", recipientKey).Errorf(
157 | "Unable to load recipient, %v", err)
158 | continue
159 | }
160 |
161 | // TODO: We may choose to loosen this check
162 | if bytes.Equal((*recipientKey)[:], (*senderPubKey)[:]) {
163 | log.WithField("recipientKey", recipientKey).Errorf(
164 | "Sender cannot be recipient, %v", err)
165 | continue
166 | }
167 |
168 | sharedKey := s.resolveSharedKey(senderPrivKey, senderPubKey, recipientKey)
169 | sealedBox := sealPayload(epl.RecipientNonce, masterKey, sharedKey)
170 |
171 | epl.RecipientBoxes[i] = sealedBox
172 | }
173 |
174 | encodedEpl := api.EncodePayloadWithRecipients(epl, recipients)
175 | digest, err := s.storePayload(epl, encodedEpl)
176 |
177 | if !toSelf {
178 | for i, recipient := range recipients {
179 | recipientEpl := api.EncryptedPayload{
180 | Sender: senderPubKey,
181 | CipherText: epl.CipherText,
182 | Nonce: epl.Nonce,
183 | RecipientBoxes: [][]byte{epl.RecipientBoxes[i]},
184 | RecipientNonce: epl.RecipientNonce,
185 | }
186 |
187 | log.WithFields(log.Fields{
188 | "recipient": hex.EncodeToString(recipient), "digest": hex.EncodeToString(digest),
189 | }).Debug("Publishing payload")
190 |
191 | s.publishPayload(recipientEpl, recipient)
192 | }
193 | }
194 |
195 | return digest, err
196 | }
197 |
198 | func createEncryptedPayload(
199 | message *[]byte, senderPubKey nacl.Key, recipients [][]byte) (api.EncryptedPayload, nacl.Key) {
200 |
201 | nonce := nacl.NewNonce()
202 | masterKey := nacl.NewKey()
203 | recipientNonce := nacl.NewNonce()
204 |
205 | sealedMessage := secretbox.Seal([]byte{}, *message, nonce, masterKey)
206 |
207 | return api.EncryptedPayload{
208 | Sender: senderPubKey,
209 | CipherText: sealedMessage,
210 | Nonce: nonce,
211 | RecipientBoxes: make([][]byte, len(recipients)),
212 | RecipientNonce: recipientNonce,
213 | }, masterKey
214 | }
215 |
216 | func (s *SecureEnclave) publishPayload(epl api.EncryptedPayload, recipient []byte) {
217 |
218 | key, err := utils.ToKey(recipient)
219 | if err != nil {
220 | log.WithField("recipient", recipient).Errorf(
221 | "Unable to decode key for recipient, error: %v", err)
222 | }
223 |
224 | if url, ok := s.PartyInfo.GetRecipient(key); ok {
225 | encoded := api.EncodePayloadWithRecipients(epl, [][]byte{})
226 | if s.grpc {
227 | api.PushGrpc(encoded, url, epl)
228 | } else {
229 | api.Push(encoded, url, s.client)
230 | }
231 | } else {
232 | log.WithField("recipientKey", hex.EncodeToString(recipient)).Error("Unable to resolve host")
233 | }
234 | }
235 |
236 | func (s *SecureEnclave) resolveSharedKey(
237 | senderPrivKey, senderPubKey, recipientPubKey nacl.Key) nacl.Key {
238 |
239 | keyCache, ok := s.keyCache[senderPubKey]
240 | if !ok {
241 | keyCache = make(map[nacl.Key]nacl.Key)
242 | s.keyCache[senderPubKey] = keyCache
243 | }
244 |
245 | sharedKey, ok := keyCache[recipientPubKey]
246 | if !ok {
247 | sharedKey = box.Precompute(recipientPubKey, senderPrivKey)
248 | keyCache[recipientPubKey] = sharedKey
249 | }
250 |
251 | return sharedKey
252 | }
253 |
254 | func (s *SecureEnclave) resolvePrivateKey(publicKey nacl.Key) (nacl.Key, error) {
255 | for i, key := range s.PubKeys {
256 | if bytes.Equal((*publicKey)[:], (*key)[:]) {
257 | return s.PrivKeys[i], nil
258 | }
259 | }
260 | return nil, fmt.Errorf("unable to find private key for public key: %s",
261 | hex.EncodeToString((*publicKey)[:]))
262 | }
263 |
264 | // Store a binary encoded payload within this SecureEnclave.
265 | // This will be a payload that has been propagated to this node as it is a party on the
266 | // transaction. I.e. it is not the original recipient of the transaction, but one of the recipients
267 | // it is intended for.
268 | func (s *SecureEnclave) StorePayload(encoded []byte) ([]byte, error) {
269 | epl, _ := api.DecodePayloadWithRecipients(encoded)
270 | return s.storePayload(epl, encoded)
271 | }
272 |
273 | func (s *SecureEnclave) StorePayloadGrpc(epl api.EncryptedPayload, encoded []byte) ([]byte, error) {
274 | return s.storePayload(epl, encoded)
275 | }
276 |
277 | func (s *SecureEnclave) storePayload(epl api.EncryptedPayload, encoded []byte) ([]byte, error) {
278 | digestHash := utils.Sha3Hash(epl.CipherText)
279 | err := s.Db.Write(&digestHash, &encoded)
280 | return digestHash, err
281 | }
282 |
283 | func sealPayload(
284 | recipientNonce nacl.Nonce,
285 | masterKey nacl.Key,
286 | sharedKey nacl.Key) []byte {
287 |
288 | return box.SealAfterPrecomputation(
289 | []byte{},
290 | (*masterKey)[:],
291 | recipientNonce,
292 | sharedKey)
293 | }
294 |
295 | // RetrieveDefault is used to retrieve the provided payload. It attempts to use a default key
296 | // value of the first public key associated with this SecureEnclave instance.
297 | // If the payload cannot be found, or decrypted successfully an error is returned.
298 | func (s *SecureEnclave) RetrieveDefault(digestHash *[]byte) ([]byte, error) {
299 | // to address is either default or specified on communication
300 | key := (*s.PubKeys[0])[:]
301 | return s.Retrieve(digestHash, &key)
302 | }
303 |
304 | // Retrieve is used to retrieve the provided payload.
305 | // If the payload cannot be found, or decrypted successfully an error is returned.
306 | func (s *SecureEnclave) Retrieve(digestHash *[]byte, to *[]byte) ([]byte, error) {
307 |
308 | encoded, err := s.Db.Read(digestHash)
309 | if err != nil {
310 | return nil, err
311 | }
312 |
313 | epl, recipients := api.DecodePayloadWithRecipients(*encoded)
314 |
315 | masterKey := new([nacl.KeySize]byte)
316 |
317 | var senderPubKey, senderPrivKey, recipientPubKey, sharedKey nacl.Key
318 |
319 | if len(recipients) == 0 {
320 | // This is a payload originally sent to us by another node
321 | recipientPubKey = epl.Sender
322 | senderPubKey, err = utils.ToKey(*to)
323 | if err != nil {
324 | return nil, err
325 | }
326 | } else {
327 | // This is a payload that originated from us
328 | senderPubKey = epl.Sender
329 | recipientPubKey, err = utils.ToKey(recipients[0])
330 | if err != nil {
331 | return nil, err
332 | }
333 | }
334 |
335 | senderPrivKey, err = s.resolvePrivateKey(senderPubKey)
336 | if err != nil {
337 | return nil, err
338 | }
339 |
340 | // we might not have the key in our cache if constellation was restarted, hence we may
341 | // need to recreate
342 | sharedKey = s.resolveSharedKey(senderPrivKey, senderPubKey, recipientPubKey)
343 |
344 | _, ok := secretbox.Open(masterKey[:0], epl.RecipientBoxes[0], epl.RecipientNonce, sharedKey)
345 | if !ok {
346 | return nil, errors.New("unable to open master key secret box")
347 | }
348 |
349 | var payload []byte
350 | payload, ok = secretbox.Open(payload[:0], epl.CipherText, epl.Nonce, masterKey)
351 | if !ok {
352 | return payload, errors.New("unable to open payload secret box")
353 | }
354 |
355 | return payload, nil
356 | }
357 |
358 | // RetrieveFor retrieves a payload with the given digestHash for a specific recipient who was one
359 | // of the original recipients specified on the payload.
360 | func (s *SecureEnclave) RetrieveFor(digestHash *[]byte, reqRecipient *[]byte) (*[]byte, error) {
361 | encoded, err := s.Db.Read(digestHash)
362 | if err != nil {
363 | return nil, err
364 | }
365 |
366 | epl, recipients := api.DecodePayloadWithRecipients(*encoded)
367 |
368 | for i, recipient := range recipients {
369 | if bytes.Equal(*reqRecipient, recipient) {
370 | recipientEpl := api.EncryptedPayload{
371 | Sender: epl.Sender,
372 | CipherText: epl.CipherText,
373 | Nonce: epl.Nonce,
374 | RecipientBoxes: [][]byte{epl.RecipientBoxes[i]},
375 | RecipientNonce: epl.RecipientNonce,
376 | }
377 | encoded := api.EncodePayload(recipientEpl)
378 | return &encoded, nil
379 | }
380 | }
381 | return nil, fmt.Errorf("invalid recipient %x requested for payload", reqRecipient)
382 | }
383 |
384 | // RetrieveAllFor retrieves all payloads that the specified recipient was an original recipient
385 | // for.
386 | // Each payload found is published to the specified recipient.
387 | func (s *SecureEnclave) RetrieveAllFor(reqRecipient *[]byte) error {
388 | return s.Db.ReadAll(func(key, value *[]byte) {
389 | epl, recipients := api.DecodePayloadWithRecipients(*value)
390 |
391 | for i, recipient := range recipients {
392 | if bytes.Equal(*reqRecipient, recipient) {
393 | recipientEpl := api.EncryptedPayload{
394 | Sender: epl.Sender,
395 | CipherText: epl.CipherText,
396 | Nonce: epl.Nonce,
397 | RecipientBoxes: [][]byte{epl.RecipientBoxes[i]},
398 | RecipientNonce: epl.RecipientNonce,
399 | }
400 | func() {
401 | go s.publishPayload(recipientEpl, *reqRecipient)
402 | }()
403 | }
404 | }
405 | })
406 | }
407 |
408 | // Delete deletes the payload associated with the given digestHash from the SecureEnclave's store.
409 | func (s *SecureEnclave) Delete(digestHash *[]byte) error {
410 | return s.Db.Delete(digestHash)
411 | }
412 |
413 | // UpdatePartyInfo applies the provided binary encoded party details to the SecureEnclave's
414 | // own party details store.
415 | func (s *SecureEnclave) UpdatePartyInfo(encoded []byte) {
416 | s.PartyInfo.UpdatePartyInfo(encoded)
417 | }
418 |
419 | func (s *SecureEnclave) UpdatePartyInfoGrpc(url string, recipients map[[nacl.KeySize]byte]string, parties map[string]bool) {
420 | s.PartyInfo.UpdatePartyInfoGrpc(url, recipients, parties)
421 | }
422 |
423 | // GetEncodedPartyInfo provides this SecureEnclaves PartyInfo details in a binary encoded format.
424 | func (s *SecureEnclave) GetEncodedPartyInfo() []byte {
425 | return api.EncodePartyInfo(s.PartyInfo)
426 | }
427 |
428 | func (s *SecureEnclave) GetEncodedPartyInfoGrpc() []byte {
429 | encoded, err := json.Marshal(api.PartyInfoResponse{Payload: api.EncodePartyInfo(s.PartyInfo)})
430 | if err != nil {
431 | log.Errorf("Marshalling failed %v", err)
432 | }
433 | return encoded
434 | }
435 |
436 | func (s *SecureEnclave) GetPartyInfo() (string, map[[nacl.KeySize]byte]string, map[string]bool) {
437 | return s.PartyInfo.GetAllValues()
438 | }
439 |
440 | func loadPubKeys(pubKeyFiles []string) ([]nacl.Key, error) {
441 | return loadKeys(
442 | pubKeyFiles,
443 | func(s string) (string, error) {
444 | src, err := ioutil.ReadFile(s)
445 | if err != nil {
446 | return "", err
447 | }
448 | return string(src), nil
449 | })
450 | }
451 |
452 | func loadPrivKeys(privKeyFiles []string) ([]nacl.Key, error) {
453 | return loadKeys(
454 | privKeyFiles,
455 | func(s string) (string, error) {
456 | var privateKey api.PrivateKey
457 | src, err := ioutil.ReadFile(s)
458 | if err != nil {
459 | return "", err
460 | }
461 | err = json.Unmarshal(src, &privateKey)
462 | if err != nil {
463 | return "", err
464 | }
465 |
466 | return privateKey.Data.Bytes, nil
467 | })
468 | }
469 |
470 | func loadKeys(
471 | keyFiles []string, f func(string) (string, error)) ([]nacl.Key, error) {
472 | keys := make([]nacl.Key, len(keyFiles))
473 |
474 | for i, keyFile := range keyFiles {
475 | data, err := f(keyFile)
476 | if err != nil {
477 | return nil, err
478 | }
479 | var key nacl.Key
480 | key, err = utils.LoadBase64Key(
481 | strings.TrimSuffix(data, "\n"))
482 | if err != nil {
483 | return nil, err
484 | }
485 | keys[i] = key
486 | }
487 |
488 | return keys, nil
489 | }
490 |
491 | // DoKeyGeneration is used to generate new public and private key-pairs, writing them to the
492 | // provided file locations.
493 | // Public keys have the "pub" suffix, whereas private keys have the "key" suffix.
494 | func DoKeyGeneration(keyFile string) error {
495 | pubKey, privKey, err := box.GenerateKey(rand.Reader)
496 | if err != nil {
497 | return fmt.Errorf("error creating keys: %v", err)
498 | }
499 | err = utils.CreateDirForFile(keyFile)
500 | if err != nil {
501 | return fmt.Errorf("invalid destination specified: %s, error: %v",
502 | filepath.Dir(keyFile), err)
503 | }
504 |
505 | b64PubKey := base64.StdEncoding.EncodeToString((*pubKey)[:])
506 | b64PrivKey := base64.StdEncoding.EncodeToString((*privKey)[:])
507 |
508 | err = ioutil.WriteFile(keyFile+".pub", []byte(b64PubKey), 0600)
509 | if err != nil {
510 | return fmt.Errorf("unable to write public key: %s, error: %v", keyFile, err)
511 | }
512 |
513 | jsonKey := api.PrivateKey{
514 | Type: "unlocked",
515 | Data: api.PrivateKeyBytes{
516 | Bytes: b64PrivKey,
517 | },
518 | }
519 |
520 | var encoded []byte
521 | encoded, err = json.Marshal(jsonKey)
522 | if err != nil {
523 | return fmt.Errorf("unable to encode private key: %v, error: %v", jsonKey, err)
524 | }
525 |
526 | err = ioutil.WriteFile(keyFile+".key", encoded, 0600)
527 | if err != nil {
528 | return fmt.Errorf("unable to write private key: %s, error: %v", keyFile, err)
529 | }
530 | return nil
531 | }
532 |
--------------------------------------------------------------------------------