├── .circleci └── config.yml ├── .dockerignore ├── .github └── pull_request_template.md ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── CLA.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.ci ├── LICENSE ├── Makefile ├── README.md ├── TERMS_OF_SERVICE ├── codecov.yml ├── deps └── cfssl │ ├── cfssl.json │ ├── intermediate.json │ ├── root.json │ └── vault.json ├── docker-compose.dev.yml ├── docker-compose.yml ├── go.mod ├── go.sum ├── img └── QuorumLogo_Blue.png ├── main.go ├── scripts ├── coverage.sh ├── generate-pki.sh ├── vault-agent-init.sh ├── vault-init-dev.sh └── vault-init.sh └── src ├── pkg ├── crypto │ ├── eddsa.go │ └── ethereum │ │ ├── encode.go │ │ ├── signing.go │ │ └── utils.go ├── encoding │ └── base64.go ├── errors │ ├── data.go │ ├── errors.go │ ├── internal.go │ └── storage.go └── log │ └── logger.go ├── service ├── errors │ └── responses.go ├── ethereum │ ├── create.go │ ├── create_test.go │ ├── ethereum.go │ ├── ethereum_test.go │ ├── get.go │ ├── get_test.go │ ├── import.go │ ├── import_test.go │ ├── list-namespaces.go │ ├── list.go │ ├── list_namespaces_test.go │ ├── list_test.go │ ├── sign-eea.go │ ├── sign-eea_test.go │ ├── sign-quorum-priv.go │ ├── sign-quorum-priv_test.go │ ├── sign-tx.go │ ├── sign-tx_test.go │ ├── sign.go │ └── sign_test.go ├── formatters │ ├── common.go │ ├── ethereum.go │ ├── fields.go │ ├── keys.go │ ├── migrations.go │ ├── request.go │ └── zk-snarks.go ├── keys │ ├── create.go │ ├── create_test.go │ ├── destroy.go │ ├── destroy_test.go │ ├── get.go │ ├── get_test.go │ ├── import.go │ ├── import_test.go │ ├── keys.go │ ├── keys_test.go │ ├── list-namespaces.go │ ├── list.go │ ├── list_namespaces_test.go │ ├── list_test.go │ ├── sign.go │ ├── sign_test.go │ ├── update.go │ └── update_test.go ├── migrations │ ├── eth-to-keys.go │ └── migrations.go └── zk-snarks │ ├── create.go │ ├── create_test.go │ ├── get.go │ ├── get_test.go │ ├── list-namespaces.go │ ├── list.go │ ├── list_namespaces_test.go │ ├── list_test.go │ ├── sign.go │ ├── sign_test.go │ ├── zk-snarks.go │ └── zk-snarks_test.go ├── utils ├── examples.go ├── faker.go ├── mocks │ └── storage.go ├── snarks.go └── storage.go ├── vault.go ├── vault ├── builder │ ├── ethereum.go │ ├── keys.go │ ├── migrations.go │ └── zk-snarks.go ├── entities │ ├── curve.go │ ├── eth_account.go │ ├── eth_params.go │ ├── key.go │ ├── migrations.go │ ├── signing_algo.go │ ├── testutils │ │ ├── ethereum_faker.go │ │ └── utils.go │ └── zks_account.go ├── storage │ ├── json.go │ ├── namespace.go │ └── namespace_test.go └── use-cases │ ├── ethereum.go │ ├── ethereum │ ├── create_account.go │ ├── create_account_test.go │ ├── get_account.go │ ├── get_account_test.go │ ├── list_accounts.go │ ├── list_accounts_test.go │ ├── list_namespaces.go │ ├── list_namespaces_test.go │ ├── sign_eea_transaction.go │ ├── sign_eea_transaction_test.go │ ├── sign_payload.go │ ├── sign_payload_test.go │ ├── sign_quorum_priv_tx.go │ ├── sign_quorum_priv_tx_test.go │ ├── sign_transaction.go │ └── sign_transaction_test.go │ ├── keys.go │ ├── keys │ ├── create_key.go │ ├── create_key_test.go │ ├── destroy_key.go │ ├── destroy_key_test.go │ ├── get_key.go │ ├── get_key_test.go │ ├── list_keys.go │ ├── list_keys_test.go │ ├── list_namespaces.go │ ├── list_namespaces_test.go │ ├── sign_payload.go │ ├── sign_payload_test.go │ ├── update_key.go │ └── update_key_test.go │ ├── migrations.go │ ├── migrations │ └── eth_to_keys.go │ ├── mocks │ ├── ethereum.go │ ├── keys.go │ ├── migrations.go │ └── zk-snarks.go │ ├── zk-snarks.go │ └── zk-snarks │ ├── create_account.go │ ├── create_account_test.go │ ├── get_account.go │ ├── get_account_test.go │ ├── list_accounts.go │ ├── list_accounts_test.go │ ├── list_namespaces.go │ ├── list_namespaces_test.go │ ├── sign_payload.go │ └── sign_payload_test.go └── vault_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # Source code 2 | .git 3 | .gitignore 4 | .dockerignore 5 | Dockerfile* 6 | docker-compose* 7 | .gitlab-ci.yml 8 | 9 | vendor/ 10 | 11 | AUTHORS 12 | CONTRIBUTING.md 13 | Makefile 14 | README.md 15 | 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## PR Description 4 | 5 | ## Fixed Issue(s) 6 | 7 | 8 | 9 | 10 | ## Documentation 11 | 12 | - [ ] I thought about documentation and added the `documentation` label to this PR if updates are required. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | __pycache__ 8 | bin/ 9 | vendor/ 10 | .env 11 | env.sh 12 | 13 | # Test binary, build with `go test -c` 14 | *.test 15 | 16 | # Jetbrains files 17 | .DS_Store 18 | .idea 19 | *.iml 20 | .idea_modules/ 21 | .run/ 22 | 23 | # Build 24 | build/ 25 | cmake-build-*/ 26 | 27 | # File-based project format 28 | *.iws 29 | *.ipr 30 | 31 | # JIRA plugin 32 | atlassian-ide-plugin.xml 33 | 34 | # Go packages and sources: 35 | .gocache 36 | 37 | # Visual Code Studio 38 | .vscode 39 | 40 | !/**/**/.gitkeep -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | # This is the official list of Quorum Hashicorp Vault Plugin Core authors 3 | 4 | Dario Varela 5 | Christian Tran 6 | Gabriel Garrido Calvo 7 | Julien Marchand 8 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## v1.1.5 (Unreleased) 6 | - Add setcap cmd on plugin file at container level 7 | - Align release artifact with docker image 8 | 9 | ## v1.1.4 (2022-01-03) 10 | - Support for optional SSL mode within init script 11 | - Support for optional kv-v2 engine within init script 12 | - Include vault-agent init script 13 | 14 | ## v1.1.3 (2021-12-22) 15 | ### 🛠 Bug fixes 16 | - Allow empty namespace in migration script 17 | 18 | ## v1.1.2 (2021-10-6) 19 | ### 🆕 Features 20 | - Added migration namespace script 21 | 22 | ## v1.1.1 (2021-10-8) 23 | ### 🆕 Features 24 | - Publishing of docker hub images 25 | 26 | ## v1.1.0 (2021-10-6) 27 | 28 | ### ⚠ BREAKING CHANGES 29 | - Renamed `bn254` by `babyjubjub` 30 | - Migrated `/ethereum` namespace to `/keys` 31 | 32 | ## v1.0.0 (2021-09-22) 33 | ### 🆕 Features 34 | - Support for key operations on Hashicorp 35 | - Supported curves: `bn254` and `secp256k1` 36 | - Supported signing algorithms: `ecdsa` and `eddsa` 37 | -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | # Sign the CLA 2 | 3 | This page is the step-by-step guide to signing the Consensys Software Inc. 4 | Individual Contributor License Agreement. 5 | 6 | 1. First and foremost, read [the current version of the CLA]. 7 | It is written to be as close to plain English as possible. 8 | 9 | 2. Make an account on [GitHub] if you don't already have one. 10 | 11 | 3. After creating your first pull request, you will see a merge 12 | pre-requisite requiring to you read and sign the CLA. 13 | 14 | If you have any questions, you can reach us at [orchestrate@consensys.net]. 15 | 16 | [github]: https://github.com/ 17 | [the current version of the cla]: https://gist.github.com/rojotek/978b48a5e8b68836856a8961d6887992 18 | [orchestrate@consensys.net]: mailto:orchestrate@consensys.net?subject=Orchestrate+Node+SDK 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ############################ 2 | # STEP 1 build executable plugin binary 3 | ############################ 4 | FROM golang:1.16-buster AS builder 5 | 6 | ARG TARGETOS 7 | ARG TARGETARCH 8 | 9 | RUN apt-get update && \ 10 | apt-get install --no-install-recommends -y \ 11 | ca-certificates upx-ucl 12 | 13 | WORKDIR /plugin 14 | 15 | ENV GO111MODULE=on 16 | COPY go.mod go.sum ./ 17 | COPY LICENSE ./ 18 | RUN go mod download 19 | 20 | COPY . . 21 | 22 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-s -w" -a -v -o quorum-hashicorp-vault-plugin 23 | RUN upx quorum-hashicorp-vault-plugin 24 | RUN sha256sum -b quorum-hashicorp-vault-plugin | cut -d' ' -f1 > SHA256SUM 25 | 26 | ############################ 27 | # STEP 2 build new vault image 28 | ############################ 29 | FROM library/vault:1.8.4 30 | 31 | RUN apk add --no-cache \ 32 | jq \ 33 | curl 34 | 35 | # Expose the plugin directory as a volume 36 | VOLUME /vault/plugins 37 | 38 | COPY --from=builder /plugin/LICENSE / 39 | COPY --from=builder /plugin/quorum-hashicorp-vault-plugin /vault/plugins/quorum-hashicorp-vault-plugin 40 | COPY --from=builder /plugin/SHA256SUM /vault/plugins/SHA256SUM 41 | COPY --from=builder /plugin/scripts/* /usr/local/bin/ 42 | 43 | RUN setcap cap_ipc_lock=+ep /vault/plugins/quorum-hashicorp-vault-plugin 44 | 45 | EXPOSE 8200 46 | -------------------------------------------------------------------------------- /Dockerfile.ci: -------------------------------------------------------------------------------- 1 | FROM library/vault:1.8.4 2 | 3 | RUN apk add --no-cache \ 4 | jq \ 5 | curl 6 | 7 | # Expose the plugin directory as a volume 8 | VOLUME /vault/plugins 9 | 10 | COPY LICENSE /LICENSE 11 | COPY ./build/bin/quorum-hashicorp-vault-plugin /vault/plugins/quorum-hashicorp-vault-plugin 12 | COPY ./build/bin/SHA256SUM /vault/plugins/SHA256SUM 13 | COPY ./scripts/* /usr/local/bin/ 14 | 15 | RUN setcap cap_ipc_lock=+ep /vault/plugins/quorum-hashicorp-vault-plugin 16 | 17 | EXPOSE 8200 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .ONESHELL: 2 | GOFILES := $(shell find . -name '*.go' -not -path "./vendor/*" | egrep -v "^\./\.go" | grep -v _test.go) 3 | DATE = $(shell date +'%s') 4 | 5 | .PHONY: build lint 6 | 7 | UNAME_S := $(shell uname -s) 8 | ifeq ($(UNAME_S),Linux) 9 | OPEN = xdg-open 10 | endif 11 | ifeq ($(UNAME_S),Darwin) 12 | OPEN = open 13 | endif 14 | 15 | test: 16 | @mkdir -p build/coverage 17 | go test ./... -cover -coverprofile=build/coverage/coverage.out -covermode=count 18 | 19 | run-coverage: test 20 | @sh scripts/coverage.sh build/coverage/coverage.out build/coverage/coverage.html 21 | 22 | coverage: run-coverage 23 | @$(OPEN) build/coverage/coverage.html 2>/dev/null 24 | 25 | gobuild: 26 | @CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -a -o build/bin/quorum-hashicorp-vault-plugin 27 | 28 | lint-tools: ## Install linting tools 29 | @GO111MODULE=on go get github.com/client9/misspell/cmd/misspell@v0.3.4 30 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.27.0 31 | 32 | lint: 33 | @misspell -w $(GOFILES) 34 | @golangci-lint run --fix 35 | 36 | lint-ci: ## Check linting 37 | @misspell -error $(GOFILES) 38 | @golangci-lint run 39 | 40 | prod: gobuild 41 | @docker-compose -f docker-compose.yml up --build vault 42 | 43 | dev: gobuild 44 | @docker-compose -f docker-compose.dev.yml up --build vault 45 | 46 | down: 47 | @docker-compose -f docker-compose.dev.yml down --volumes --timeout 0 48 | 49 | docker-build: 50 | @DOCKER_BUILDKIT=1 docker build -t quorum-hashicorp-vault-plugin . 51 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: false 3 | 4 | coverage: 5 | range: 50..100 6 | round: down 7 | precision: 2 8 | status: 9 | project: 10 | default: 11 | target: 80% 12 | # Allow decreasing 2% of total coverage to avoid noise. 13 | threshold: 2% 14 | if_ci_failed: error 15 | informational: true 16 | paths: 17 | - "src" 18 | - "pkg" 19 | - "!*/mocks" 20 | - "!*/testutils" 21 | patch: 22 | default: 23 | target: 90% 24 | threshold: 0% 25 | informational: true 26 | paths: 27 | - "src" 28 | - "pkg" 29 | - "!*/mocks" 30 | - "!*/testutils" 31 | 32 | comment: 33 | layout: "reach, diff, flags, files" 34 | behavior: default 35 | require_changes: no 36 | 37 | ignore: 38 | - main.go 39 | - "*/testutils" 40 | - "*/mocks" 41 | -------------------------------------------------------------------------------- /deps/cfssl/cfssl.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "8760h" 5 | }, 6 | "profiles": { 7 | "intermediate_ca": { 8 | "usages": [ 9 | "signing", 10 | "digital signature", 11 | "key encipherment", 12 | "cert sign", 13 | "crl sign", 14 | "server auth", 15 | "client auth" 16 | ], 17 | "expiry": "8760h", 18 | "ca_constraint": { 19 | "is_ca": true, 20 | "max_path_len": 0, 21 | "max_path_len_zero": true 22 | } 23 | }, 24 | "peer": { 25 | "usages": [ 26 | "signing", 27 | "digital signature", 28 | "key encipherment", 29 | "client auth", 30 | "server auth" 31 | ], 32 | "expiry": "8760h" 33 | }, 34 | "server": { 35 | "usages": [ 36 | "signing", 37 | "digital signing", 38 | "key encipherment", 39 | "server auth" 40 | ], 41 | "expiry": "8760h" 42 | }, 43 | "client": { 44 | "usages": [ 45 | "signing", 46 | "digital signature", 47 | "key encipherment", 48 | "client auth", 49 | "server auth" 50 | ], 51 | "expiry": "8760h" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /deps/cfssl/intermediate.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Tanuki Intermediate CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "ST": "Paris", 12 | "O": "Consensys", 13 | "OU": "Tanuki Team Intermediate CA" 14 | } 15 | ], 16 | "ca": { 17 | "expiry": "42720h" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /deps/cfssl/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Tanuki Root CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "ST": "Paris", 12 | "O": "Consensys", 13 | "OU": "Tanuki team Root CA" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /deps/cfssl/vault.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "hashicorp", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "O": "Consensys", 12 | "OU": "Vault Tanuki", 13 | "ST": "Paris" 14 | } 15 | 16 | ], 17 | "hosts": [ 18 | "host1.example-company.com", 19 | "localhost", 20 | "127.0.0.1", 21 | "hashicorp", 22 | "vault", 23 | "vault.qa-qkm" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | x-container-common: &container-common 4 | networks: 5 | - vault 6 | 7 | services: 8 | vault: 9 | <<: *container-common 10 | image: library/vault:1.8.4 11 | restart: ${CONTAINER_RESTART-on-failure} 12 | tty: true 13 | cap_add: 14 | - IPC_LOCK 15 | volumes: 16 | - ./build/bin/quorum-hashicorp-vault-plugin:/vault/plugins/quorum-hashicorp-vault-plugin 17 | - ./scripts/vault-init-dev.sh:/usr/local/bin/vault-init.sh 18 | environment: 19 | VAULT_ADDR: http://vault:8200 20 | VAULT_DEV_ROOT_TOKEN_ID: ${VAULT_TOKEN-DevVaultToken} 21 | entrypoint: 22 | - sh 23 | - -c 24 | - | 25 | apk add --no-cache curl 26 | ( sleep 2 ; vault-init.sh ) & 27 | vault server -dev -dev-plugin-dir=/vault/plugins/ -dev-listen-address="0.0.0.0:8200" -log-level=trace 28 | ports: 29 | - 8200:8200 30 | healthcheck: 31 | test: [ "CMD", "wget", "--spider", "--proxy", "off", "http://localhost:8200/v1/sys/health?standbyok=true" ] 32 | interval: 10s 33 | timeout: 3s 34 | retries: 10 35 | start_period: 5s 36 | 37 | networks: 38 | vault: 39 | driver: bridge 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/consensys/quorum-hashicorp-vault-plugin 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/consensys/gnark-crypto v0.5.0 7 | github.com/consensys/quorum v2.7.0+incompatible 8 | github.com/ethereum/go-ethereum v1.10.13 9 | github.com/golang/mock v1.4.3 10 | github.com/hashicorp/go-hclog v0.9.2 11 | github.com/hashicorp/vault/api v1.0.5-0.20200117231345-460d63e36490 12 | github.com/hashicorp/vault/sdk v0.1.14-0.20200305172021-03a3749f220d 13 | github.com/stretchr/testify v1.7.0 14 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 15 | ) 16 | 17 | replace github.com/Azure/go-autorest => github.com/Azure/go-autorest v12.4.1+incompatible 18 | 19 | // Containous forks 20 | replace ( 21 | github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e 22 | github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a 23 | ) 24 | -------------------------------------------------------------------------------- /img/QuorumLogo_Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/quorum-hashicorp-vault-plugin/ccd131fc1b063bde240afc084193b389f51697ed/img/QuorumLogo_Blue.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src" 8 | log2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/hashicorp/vault/api" 10 | "github.com/hashicorp/vault/sdk/plugin" 11 | ) 12 | 13 | func main() { 14 | client := &api.PluginAPIClientMeta{} 15 | err := client.FlagSet().Parse(os.Args[1:]) 16 | if err != nil { 17 | log.Println(err) 18 | os.Exit(1) 19 | } 20 | 21 | log2.InitLogger() 22 | 23 | err = plugin.Serve(&plugin.ServeOpts{ 24 | BackendFactoryFunc: src.NewVaultBackend, 25 | TLSProviderFunc: api.VaultPluginTLSProvider(client.GetTLSConfig()), 26 | Logger: log2.Default(), 27 | }) 28 | 29 | if err != nil { 30 | log.Println(err) 31 | os.Exit(1) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -Eeu 5 | 6 | # Ignore generated & testutils files 7 | cat $1 | grep -Fv -e "/mocks" -e "/testutils" > "$1.tmp" 8 | 9 | # Print total coverage 10 | go tool cover -func="$1.tmp" | grep total: 11 | 12 | # Generate coverage report in html format 13 | go tool cover -html="$1.tmp" -o $2 14 | 15 | cat "$1.tmp" > $1 16 | 17 | rm "$1.tmp" 18 | -------------------------------------------------------------------------------- /scripts/generate-pki.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # useful dirs 4 | TEMP_DIR=/tmp 5 | 6 | mkdir -p $DEST_CERT_PATH 7 | 8 | ROOT_CRT=$TEMP_DIR/ca.pem 9 | ROOT_KEY=$TEMP_DIR/ca-key.pem 10 | 11 | # Gen root + intermediate 12 | cfssl gencert -initca $CONF_DIR/root.json | cfssljson -bare $TEMP_DIR/ca 13 | cfssl gencert -initca $CONF_DIR/intermediate.json | cfssljson -bare $TEMP_DIR/intermediate_ca 14 | cfssl sign -ca $ROOT_CRT -ca-key $ROOT_KEY -config $CONF_DIR//cfssl.json -profile intermediate_ca $TEMP_DIR/intermediate_ca.csr | cfssljson -bare $TEMP_DIR/intermediate_ca 15 | 16 | INTER_CRT=$TEMP_DIR/intermediate_ca.pem 17 | INTER_KEY=$TEMP_DIR/intermediate_ca-key.pem 18 | 19 | # Gen leaves from intermediate 20 | cfssl gencert -ca $INTER_CRT -ca-key $INTER_KEY -config $CONF_DIR/cfssl.json -profile=server $CONF_DIR/vault.json | cfssljson -bare $TEMP_DIR/vault-server 21 | cfssl gencert -ca $INTER_CRT -ca-key $INTER_KEY -config $CONF_DIR/cfssl.json -profile=client $CONF_DIR/vault.json | cfssljson -bare $TEMP_DIR/vault-client 22 | 23 | # ca.crt is ca.pem >> intermediate.pem 24 | cat $TEMP_DIR/ca.pem > $TEMP_DIR/ca.crt 25 | cat $TEMP_DIR/intermediate_ca.pem >> $TEMP_DIR/ca.crt 26 | 27 | # Verify certs 28 | openssl verify -CAfile $TEMP_DIR/ca.crt $TEMP_DIR/vault-server.pem $TEMP_DIR/vault-client.pem 29 | 30 | # Relocate 31 | mv $TEMP_DIR/ca.crt $DEST_CERT_PATH/ca.crt 32 | mv $TEMP_DIR/vault-server.pem $DEST_CERT_PATH/tls.crt 33 | mv $TEMP_DIR/vault-server-key.pem $DEST_CERT_PATH/tls.key 34 | mv $TEMP_DIR/vault-client.pem $DEST_CERT_PATH/client.crt 35 | mv $TEMP_DIR/vault-client-key.pem $DEST_CERT_PATH/client.key 36 | 37 | # cleanup 38 | rm -rf $TEMP_DIR 39 | 40 | 41 | -------------------------------------------------------------------------------- /scripts/vault-agent-init.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | VAULT_TOKEN=$(cat "${ROOT_TOKEN_PATH}") 4 | VAULT_SSL_PARAMS="" 5 | if [ -n "$VAULT_CACERT" ]; then 6 | VAULT_SSL_PARAMS="$VAULT_SSL_PARAMS --cacert $VAULT_CACERT" 7 | fi 8 | 9 | if [ -n "$VAULT_CLIENT_CERT" ]; then 10 | VAULT_SSL_PARAMS="$VAULT_SSL_PARAMS --cert $VAULT_CLIENT_CERT" 11 | fi 12 | 13 | if [ -n "$VAULT_CLIENT_KEY" ]; then 14 | VAULT_SSL_PARAMS="$VAULT_SSL_PARAMS --key $VAULT_CLIENT_KEY" 15 | fi 16 | 17 | echo "[AGENT] Enabling approle auth" 18 | curl -s --header "X-Vault-Token: ${VAULT_TOKEN}" --request POST ${VAULT_SSL_PARAMS} \ 19 | --data '{"type": "approle"}' \ 20 | ${VAULT_ADDR}/v1/sys/auth/approle 21 | 22 | echo "[AGENT] Adding policy capabilities '${CAPABILITIES}' to path '${PLUGIN_MOUNT_PATH}/*'" 23 | curl -s --header "X-Vault-Token: $VAULT_TOKEN" --request PUT ${VAULT_SSL_PARAMS} \ 24 | --data '{ "policy":"path \"'"${PLUGIN_MOUNT_PATH}/*"'\" { capabilities = '"${CAPABILITIES}"' }" }' \ 25 | ${VAULT_ADDR}/v1/sys/policies/acl/${POLICY_ID} 26 | 27 | if [ -n "$KVV2_MOUNT_PATH" ]; then 28 | echo "[AGENT] Adding policy capabilities '${CAPABILITIES}' to path '${KVV2_MOUNT_PATH}/*'" 29 | curl -s --header "X-Vault-Token: $VAULT_TOKEN" --request PUT ${VAULT_SSL_PARAMS} \ 30 | --data '{ "policy":"path \"'"${KVV2_MOUNT_PATH}/*"'\" { capabilities = '"${CAPABILITIES}"' }" }' \ 31 | ${VAULT_ADDR}/v1/sys/policies/acl/${KVV2_POLICY_ID} 32 | fi 33 | 34 | echo "[AGENT] Create an AppRole '${APP_ROLE_ID}' with desired set of policies '${APP_ROLE_POLICIES}'" 35 | curl -s --header "X-Vault-Token: $VAULT_TOKEN" --request POST ${VAULT_SSL_PARAMS} \ 36 | --data '{"policies": '"${APP_ROLE_POLICIES}"'}' \ 37 | ${VAULT_ADDR}/v1/auth/approle/role/${APP_ROLE_ID} 38 | 39 | echo "[AGENT] Fetching role identifier" 40 | curl -s --header "X-Vault-Token: $VAULT_TOKEN" ${VAULT_SSL_PARAMS} \ 41 | ${VAULT_ADDR}/v1/auth/approle/role/${APP_ROLE_ID}/role-id > role.json 42 | ROLE_ID=$(cat role.json | jq .data.role_id | tr -d '"') 43 | echo $ROLE_ID > ${ROLE_FILE_PATH} 44 | rm role.json 45 | 46 | echo "[AGENT] Fetching role secret" 47 | curl -s --header "X-Vault-Token: $VAULT_TOKEN" --request POST ${VAULT_SSL_PARAMS} \ 48 | ${VAULT_ADDR}/v1/auth/approle/role/${APP_ROLE_ID}/secret-id > secret.json 49 | SECRET_ID=$(cat secret.json | jq .data.secret_id | tr -d '"') 50 | echo $SECRET_ID > ${SECRET_FILE_PATH} 51 | rm secret.json 52 | -------------------------------------------------------------------------------- /scripts/vault-init-dev.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | VAULT_ADDR=${VAULT_ADDR-localhost:8200} 4 | PLUGIN_MOUNT_PATH=${PLUGIN_MOUNT_PATH-quorum} 5 | PLUGIN_PATH=${PLUGIN_PATH-/vault/plugins} 6 | VAULT_DEV_ROOT_TOKEN_ID=${VAULT_DEV_ROOT_TOKEN_ID-DevVaultToken} 7 | 8 | if [ "${PLUGIN_PATH}" != "/vault/plugins" ]; then 9 | mkdir -p ${PLUGIN_PATH} 10 | echo "[INIT] Copying plugin to expected folder" 11 | cp $PLUGIN_FILE "${PLUGIN_PATH}/quorum-hashicorp-vault-plugin" 12 | fi 13 | 14 | echo "[INIT] Enabling Quorum Hashicorp Plugin engine..." 15 | curl --header "X-Vault-Token: ${VAULT_DEV_ROOT_TOKEN_ID}" --request POST \ 16 | --data '{"type": "plugin", "plugin_name": "quorum-hashicorp-vault-plugin", "config": {"force_no_cache": true, "passthrough_request_headers": ["X-Vault-Namespace"]} }' \ 17 | ${VAULT_ADDR}/v1/sys/mounts/${PLUGIN_MOUNT_PATH} 18 | 19 | -------------------------------------------------------------------------------- /scripts/vault-init.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | # Store root token in a file so it can be shared with other services through volume 3 | # Init Vault 4 | 5 | VAULT_ADDR=${VAULT_ADDR-localhost:8200} 6 | PLUGIN_PATH=${PLUGIN_PATH-/vault/plugins} 7 | PLUGIN_MOUNT_PATH=${PLUGIN_MOUNT_PATH-quorum} 8 | ROOT_TOKEN_PATH=${ROOT_TOKEN_PATH-/vault/.root} 9 | PLUGIN_FILE=/vault/plugins/quorum-hashicorp-vault-plugin 10 | 11 | VAULT_SSL_PARAMS="" 12 | if [ -n "$VAULT_CACERT" ]; then 13 | VAULT_SSL_PARAMS="$VAULT_SSL_PARAMS --cacert $VAULT_CACERT" 14 | fi 15 | 16 | if [ -n "$VAULT_CLIENT_CERT" ]; then 17 | VAULT_SSL_PARAMS="$VAULT_SSL_PARAMS --cert $VAULT_CLIENT_CERT" 18 | fi 19 | 20 | if [ -n "$VAULT_CLIENT_KEY" ]; then 21 | VAULT_SSL_PARAMS="$VAULT_SSL_PARAMS --key $VAULT_CLIENT_KEY" 22 | fi 23 | 24 | echo "[INIT] Initializing Vault: ${VAULT_ADDR}" 25 | 26 | curl -s --request POST ${VAULT_SSL_PARAMS} \ 27 | --data '{"secret_shares": 1, "secret_threshold": 1}' ${VAULT_ADDR}/v1/sys/init > response.json 28 | 29 | ROOT_TOKEN=$(cat response.json | jq .root_token | tr -d '"') 30 | UNSEAL_KEY=$(cat response.json | jq .keys | jq .[0]) 31 | ERRORS=$(cat response.json | jq .errors | jq .[0]) 32 | rm response.json 33 | 34 | if [ "$UNSEAL_KEY" = "null" ]; then 35 | echo "[INIT] cannot retrieve unseal key: $ERRORS" 36 | exit 1 37 | fi 38 | 39 | # Unseal Vault 40 | echo "[INIT] Unsealing vault..." 41 | curl -s --request POST ${VAULT_SSL_PARAMS} \ 42 | --data '{"key": '${UNSEAL_KEY}'}' ${VAULT_ADDR}/v1/sys/unseal 43 | 44 | if [ "${PLUGIN_PATH}" != "/vault/plugins" ]; then 45 | mkdir -p ${PLUGIN_PATH} 46 | echo "[INIT] Copying plugin to expected folder" 47 | cp $PLUGIN_FILE "${PLUGIN_PATH}/quorum-hashicorp-vault-plugin" 48 | fi 49 | 50 | echo "[INIT] Registering Quorum Hashicorp Vault plugin..." 51 | SHA256SUM=$(sha256sum -b ${PLUGIN_FILE} | cut -d' ' -f1) 52 | curl -s --header "X-Vault-Token: ${ROOT_TOKEN}" --request POST ${VAULT_SSL_PARAMS} \ 53 | --data "{\"sha256\": \"${SHA256SUM}\", \"command\": \"quorum-hashicorp-vault-plugin\" }" \ 54 | ${VAULT_ADDR}/v1/sys/plugins/catalog/secret/quorum-hashicorp-vault-plugin 55 | 56 | echo "[INIT] Enabling Quorum Hashicorp Vault engine..." 57 | curl -s --header "X-Vault-Token: ${ROOT_TOKEN}" --request POST ${VAULT_SSL_PARAMS} \ 58 | --data '{"type": "plugin", "plugin_name": "quorum-hashicorp-vault-plugin", "config": {"force_no_cache": true, "passthrough_request_headers": ["X-Vault-Namespace"]} }' \ 59 | ${VAULT_ADDR}/v1/sys/mounts/${PLUGIN_MOUNT_PATH} 60 | 61 | if [ -n "$KVV2_MOUNT_PATH" ]; then 62 | echo "[INIT] Enabling kv-v2 Hashicorp Vault engine..." 63 | curl --header "X-Vault-Token: ${ROOT_TOKEN}" --request POST ${VAULT_SSL_PARAMS}\ 64 | --data '{"type": "kv-v2", "config": {"force_no_cache": true} }' \ 65 | ${VAULT_ADDR}/v1/sys/mounts/${KVV2_MOUNT_PATH} 66 | fi 67 | 68 | if [ -n "$ROOT_TOKEN" ]; then 69 | echo "[INIT] Root token saved in ${ROOT_TOKEN_PATH}" 70 | echo "$ROOT_TOKEN" > ${ROOT_TOKEN_PATH} 71 | fi 72 | 73 | exit 0 74 | -------------------------------------------------------------------------------- /src/pkg/crypto/eddsa.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | 7 | babyjubjub "github.com/consensys/gnark-crypto/ecc/bn254/twistededwards/eddsa" 8 | ) 9 | 10 | func NewBabyjubjub() (babyjubjub.PrivateKey, error) { 11 | seed := make([]byte, 32) 12 | _, err := rand.Read(seed) 13 | if err != nil { 14 | return babyjubjub.PrivateKey{}, err 15 | } 16 | 17 | // Usually standards implementations of eddsa do not require the choice of a specific hash function (usually it's SHA256). 18 | // Here we needed to allow the choice of the hash, so we can choose a hash function that is easily programmable in a snark circuit. 19 | // Same hFunc should be used for sign and verify 20 | return babyjubjub.GenerateKey(bytes.NewReader(seed)) 21 | } 22 | -------------------------------------------------------------------------------- /src/pkg/crypto/ethereum/encode.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/consensys/quorum/common" 5 | "github.com/consensys/quorum/rlp" 6 | "golang.org/x/crypto/sha3" 7 | ) 8 | 9 | func Hash(object interface{}) (hash common.Hash, err error) { 10 | hashAlgo := sha3.NewLegacyKeccak256() 11 | err = rlp.Encode(hashAlgo, object) 12 | if err != nil { 13 | return common.Hash{}, err 14 | } 15 | hashAlgo.Sum(hash[:0]) 16 | return hash, nil 17 | } 18 | -------------------------------------------------------------------------------- /src/pkg/crypto/ethereum/signing.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 6 | "math/big" 7 | 8 | "github.com/consensys/quorum/core/types" 9 | "github.com/consensys/quorum/crypto" 10 | ) 11 | 12 | func SignTransaction(tx *types.Transaction, privKey *ecdsa.PrivateKey, signer types.Signer) ([]byte, error) { 13 | h := signer.Hash(tx) 14 | decodedSignature, err := crypto.Sign(h[:], privKey) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return decodedSignature, nil 20 | } 21 | 22 | func SignQuorumPrivateTransaction(tx *types.Transaction, privKey *ecdsa.PrivateKey, signer types.Signer) ([]byte, error) { 23 | h := signer.Hash(tx) 24 | decodedSignature, err := crypto.Sign(h[:], privKey) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return decodedSignature, nil 30 | } 31 | 32 | func SignEEATransaction(tx *types.Transaction, privateArgs *entities.PrivateETHTransactionParams, chainID string, privKey *ecdsa.PrivateKey) ([]byte, error) { 33 | chainIDBigInt, _ := new(big.Int).SetString(chainID, 10) 34 | privateFromEncoded, err := GetEncodedPrivateFrom(privateArgs.PrivateFrom) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | privateRecipientEncoded, err := GetEncodedPrivateRecipient(privateArgs.PrivacyGroupID, privateArgs.PrivateFor) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | hash, err := Hash([]interface{}{ 45 | tx.Nonce(), 46 | tx.GasPrice(), 47 | tx.Gas(), 48 | tx.To(), 49 | tx.Value(), 50 | tx.Data(), 51 | chainIDBigInt, 52 | uint(0), 53 | uint(0), 54 | privateFromEncoded, 55 | privateRecipientEncoded, 56 | privateArgs.PrivateTxType, 57 | }) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | signature, err := crypto.Sign(hash[:], privKey) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return signature, err 68 | } 69 | -------------------------------------------------------------------------------- /src/pkg/crypto/ethereum/utils.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "encoding/base64" 5 | "math/big" 6 | 7 | "github.com/consensys/quorum/core/types" 8 | ) 9 | 10 | func GetEncodedPrivateFrom(privateFrom string) ([]byte, error) { 11 | privateFromEncoded, err := base64.StdEncoding.DecodeString(privateFrom) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | return privateFromEncoded, nil 17 | } 18 | 19 | func GetEncodedPrivateRecipient(privacyGroupID string, privateFor []string) (interface{}, error) { 20 | var privateRecipientEncoded interface{} 21 | var err error 22 | if privacyGroupID != "" { 23 | privateRecipientEncoded, err = base64.StdEncoding.DecodeString(privacyGroupID) 24 | if err != nil { 25 | return nil, err 26 | } 27 | } else { 28 | var privateForByteSlice [][]byte 29 | for _, v := range privateFor { 30 | b, der := base64.StdEncoding.DecodeString(v) 31 | if der != nil { 32 | return nil, err 33 | } 34 | privateForByteSlice = append(privateForByteSlice, b) 35 | } 36 | privateRecipientEncoded = privateForByteSlice 37 | } 38 | 39 | return privateRecipientEncoded, nil 40 | } 41 | 42 | func GetEIP155Signer(chainID string) types.Signer { 43 | chainIDBigInt := new(big.Int) 44 | chainIDBigInt, _ = chainIDBigInt.SetString(chainID, 10) 45 | return types.NewEIP155Signer(chainIDBigInt) 46 | } 47 | 48 | func GetQuorumPrivateTxSigner() types.Signer { 49 | return types.QuorumPrivateTxSigner{} 50 | } 51 | -------------------------------------------------------------------------------- /src/pkg/encoding/base64.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import "encoding/base64" 4 | 5 | func EncodeToBase64(b []byte) string { 6 | return base64.URLEncoding.EncodeToString(b) 7 | } 8 | 9 | func DecodeBase64(s string) ([]byte, error) { 10 | return base64.URLEncoding.DecodeString(s) 11 | } 12 | -------------------------------------------------------------------------------- /src/pkg/errors/data.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | const ( 4 | // Data Errors (class 42XXX) 5 | Data uint64 = 4<<16 + 2<<12 6 | Encoding = Data + 1<<8 // Invalid Encoding (subclass 421XX) 7 | InvalidFormat = Data + 2<<8 // Invalid format (subclass 423XX) 8 | InvalidParameter = Data + 3<<8 // Invalid parameter provided (subclass 424XX) 9 | Crypto = Data + 4<<8 10 | ) 11 | 12 | // EncodingError are raised when failing to decode a message 13 | func EncodingError(format string, a ...interface{}) *Error { 14 | return Errorf(Encoding, format, a...) 15 | } 16 | 17 | // IsEncodingError indicate whether an error is a EncodingError error 18 | func IsEncodingError(err error) bool { 19 | return isErrorClass(FromError(err).GetCode(), Encoding) 20 | } 21 | 22 | // InvalidFormatError is raised when a Data does not match an expected format 23 | func InvalidFormatError(format string, a ...interface{}) *Error { 24 | return Errorf(InvalidFormat, format, a...) 25 | } 26 | 27 | // IsInvalidFormatError indicate whether an error is an invalid format error 28 | func IsInvalidFormatError(err error) bool { 29 | return isErrorClass(FromError(err).GetCode(), InvalidFormat) 30 | } 31 | 32 | // InvalidParameterError is raised when a provided parameter invalid 33 | func InvalidParameterError(format string, a ...interface{}) *Error { 34 | return Errorf(InvalidParameter, format, a...) 35 | } 36 | 37 | // IsInvalidParameterError indicate whether an error is an invalid parameter error 38 | func IsInvalidParameterError(err error) bool { 39 | return isErrorClass(FromError(err).GetCode(), InvalidParameter) 40 | } 41 | 42 | func CryptoOperationError(format string, a ...interface{}) *Error { 43 | return Errorf(Crypto, format, a...) 44 | } 45 | 46 | func IsCryptoOperationError(err error) bool { 47 | return isErrorClass(FromError(err).GetCode(), Crypto) 48 | } 49 | -------------------------------------------------------------------------------- /src/pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | //nolint 2 | package errors 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | type Error struct { 9 | Message string 10 | Code uint64 11 | } 12 | 13 | func (e *Error) GetCode() uint64 { 14 | return e.Code 15 | } 16 | 17 | func (e *Error) Error() string { 18 | return fmt.Sprintf("%s", e.Message) 19 | } 20 | 21 | func (m *Error) GetMessage() string { 22 | return m.Message 23 | } 24 | 25 | func Errorf(code uint64, format string, a ...interface{}) *Error { 26 | return &Error{fmt.Sprintf(format, a...), code} 27 | } 28 | 29 | func FromError(err interface{}) *Error { 30 | if err == nil { 31 | return nil 32 | } 33 | 34 | ierr, ok := err.(*Error) 35 | if !ok { 36 | return Errorf(Internal, err.(error).Error()) 37 | } 38 | 39 | return ierr 40 | } 41 | -------------------------------------------------------------------------------- /src/pkg/errors/internal.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | // Configuration errors (class F0XXX) 9 | Config uint64 = 15 << 16 10 | 11 | // Internal errors (class FFXXX) 12 | Internal uint64 = 15<<16 + 15<<12 13 | ) 14 | 15 | //nolint 16 | var NotImplementedError = fmt.Errorf("not implemented") 17 | 18 | func isErrorClass(code, base uint64) bool { 19 | // Error codes have a 5 hex representation (<=> 20 bits representation) 20 | // - (code^base)&255<<12 compute difference between 2 first nibbles (bits 13 to 20) 21 | // - (code^base)&(base&15<<8) compute difference between 3rd nibble in case base 3rd nibble is non zero (bits 9 to 12) 22 | return (code^base)&(255<<12+15<<8&base) == 0 23 | } 24 | 25 | // InternalError is raised when an unknown exception is met 26 | func InternalError(format string, a ...interface{}) *Error { 27 | return Errorf(Internal, format, a...) 28 | } 29 | 30 | // IsInternalError indicate whether an error is an Internal error 31 | func IsInternalError(err error) bool { 32 | return isErrorClass(FromError(err).GetCode(), Internal) 33 | } 34 | 35 | // ConfigError is raised when an error is encountered while loading configuration 36 | func ConfigError(format string, a ...interface{}) *Error { 37 | return Errorf(Config, format, a...) 38 | } 39 | -------------------------------------------------------------------------------- /src/pkg/errors/storage.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | const ( 4 | // Storage Error (class DBXXX) 5 | Storage uint64 = 13<<16 + 11<<12 6 | NotFound = Storage + 2<<8 // Not found (subclass DB2XX) 7 | ConstraintViolated = Storage + 1<<8 // Storage constraint violated (subclass DB1XX) 8 | AlreadyExists = ConstraintViolated + 1 // A resource with same index already exists (code DB101) 9 | ) 10 | 11 | // NoDataFoundError is raised when accessing a missing Data 12 | func NotFoundError(format string, a ...interface{}) *Error { 13 | return Errorf(NotFound, format, a...) 14 | } 15 | 16 | // IsNotFoundError indicate whether an error is a no Data found error 17 | func IsNotFoundError(err error) bool { 18 | return isErrorClass(FromError(err).GetCode(), NotFound) 19 | } 20 | 21 | // AlreadyExistsError is raised when a Data constraint has been violated 22 | func AlreadyExistsError(format string, a ...interface{}) *Error { 23 | return Errorf(AlreadyExists, format, a...) 24 | } 25 | 26 | // IsAlreadyExistsError indicate whether an error is an already exists error 27 | func IsAlreadyExistsError(err error) bool { 28 | return isErrorClass(FromError(err).GetCode(), AlreadyExists) 29 | } 30 | 31 | func StorageError(format string, a ...interface{}) *Error { 32 | return Errorf(AlreadyExists, format, a...) 33 | } 34 | 35 | func IsStorageError(err error) bool { 36 | return isErrorClass(FromError(err).GetCode(), Storage) 37 | } 38 | -------------------------------------------------------------------------------- /src/pkg/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "sync" 7 | 8 | "github.com/hashicorp/go-hclog" 9 | ) 10 | 11 | type loggerCtxKey string 12 | 13 | const loggerKey loggerCtxKey = "logger" 14 | 15 | var protect sync.Once 16 | 17 | type Logger interface { 18 | hclog.Logger 19 | } 20 | 21 | func Context(ctx context.Context, logger Logger) context.Context { 22 | return context.WithValue(ctx, loggerKey, logger) 23 | } 24 | 25 | func FromContext(ctx context.Context) Logger { 26 | if logger, ok := ctx.Value(loggerKey).(Logger); ok { 27 | return logger 28 | } 29 | 30 | return hclog.Default() 31 | } 32 | 33 | func Default() Logger { 34 | return hclog.Default() 35 | } 36 | 37 | func InitLogger() { 38 | protect.Do(func() { 39 | logger := hclog.New(&hclog.LoggerOptions{ 40 | Level: hclog.Trace, 41 | Output: os.Stderr, 42 | JSONFormat: false, 43 | }) 44 | 45 | hclog.SetDefault(logger) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/service/errors/responses.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | pkgerrors "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/hashicorp/vault/sdk/logical" 7 | ) 8 | 9 | func ParseHTTPError(err error) (*logical.Response, error) { 10 | switch { 11 | case pkgerrors.IsNotFoundError(err): 12 | return logical.ErrorResponse(err.Error()), logical.ErrUnsupportedPath 13 | case 14 | pkgerrors.IsInvalidFormatError(err), 15 | pkgerrors.IsInvalidParameterError(err), 16 | pkgerrors.IsEncodingError(err), 17 | pkgerrors.IsAlreadyExistsError(err): 18 | return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest 19 | default: 20 | return nil, errors.New("internal server error. Please retry or contact an administrator") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/service/ethereum/create.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewCreateOperation() *framework.PathOperation { 14 | successExample := utils.Example200Response() 15 | 16 | return &framework.PathOperation{ 17 | Callback: c.createHandler(), 18 | Summary: "Creates a new Ethereum account", 19 | Description: "Creates a new Ethereum account by generating a private key, storing it in the Vault and computing its public key and address", 20 | Examples: []framework.RequestExample{ 21 | { 22 | Description: "Creates a new account on the tenant0 namespace", 23 | Response: successExample, 24 | }, 25 | }, 26 | Responses: map[int][]framework.Response{ 27 | 200: {*successExample}, 28 | 500: {utils.Example500Response()}, 29 | }, 30 | } 31 | } 32 | 33 | func (c *controller) createHandler() framework.OperationFunc { 34 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 35 | namespace := formatters.GetRequestNamespace(req) 36 | 37 | ctx = log.Context(ctx, c.logger) 38 | account, err := c.useCases.CreateAccount().WithStorage(req.Storage).Execute(ctx, namespace, "") 39 | if err != nil { 40 | return errors.ParseHTTPError(err) 41 | } 42 | 43 | return formatters.FormatAccountResponse(account), nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/service/ethereum/create_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/golang/mock/gomock" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func (s *ethereumCtrlTestSuite) TestEthereumController_Create() { 17 | path := s.controller.Paths()[0] 18 | createOperation := path.Operations[logical.CreateOperation] 19 | 20 | s.T().Run("should define the correct path", func(t *testing.T) { 21 | assert.Equal(t, "ethereum/accounts/?", path.Pattern) 22 | assert.NotEmpty(t, createOperation) 23 | }) 24 | 25 | s.T().Run("should define correct properties", func(t *testing.T) { 26 | properties := createOperation.Properties() 27 | 28 | assert.NotEmpty(t, properties.Description) 29 | assert.NotEmpty(t, properties.Summary) 30 | assert.NotEmpty(t, properties.Examples[0].Description) 31 | assert.Empty(t, properties.Examples[0].Data) 32 | assert.NotEmpty(t, properties.Examples[0].Response) 33 | assert.NotEmpty(t, properties.Responses[200]) 34 | assert.NotEmpty(t, properties.Responses[500]) 35 | }) 36 | 37 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 38 | account := apputils.FakeETHAccount() 39 | request := &logical.Request{ 40 | Storage: s.storage, 41 | Headers: map[string][]string{ 42 | formatters.NamespaceHeader: {account.Namespace}, 43 | }, 44 | } 45 | 46 | s.createAccountUC.EXPECT().Execute(gomock.Any(), account.Namespace, "").Return(account, nil) 47 | 48 | response, err := createOperation.Handler()(s.ctx, request, &framework.FieldData{}) 49 | require.NoError(t, err) 50 | 51 | assert.Equal(t, account.Address, response.Data[formatters.AddressLabel]) 52 | assert.Equal(t, account.PublicKey, response.Data[formatters.PublicKeyLabel]) 53 | assert.Equal(t, account.CompressedPublicKey, response.Data[formatters.CompressedPublicKeyLabel]) 54 | assert.Equal(t, account.Namespace, response.Data[formatters.NamespaceLabel]) 55 | }) 56 | 57 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 58 | request := &logical.Request{ 59 | Storage: s.storage, 60 | } 61 | expectedErr := errors.NotFoundError("error") 62 | 63 | s.createAccountUC.EXPECT().Execute(gomock.Any(), "", "").Return(nil, expectedErr) 64 | 65 | _, err := createOperation.Handler()(s.ctx, request, &framework.FieldData{}) 66 | 67 | assert.Equal(t, err, logical.ErrUnsupportedPath) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /src/service/ethereum/get.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewGetOperation() *framework.PathOperation { 14 | exampleAccount := utils.ExampleETHAccount() 15 | successExample := utils.Example200Response() 16 | 17 | return &framework.PathOperation{ 18 | Callback: c.getHandler(), 19 | Summary: "Gets an Ethereum account", 20 | Description: "Gets an Ethereum account stored in the vault at the given address and namespace", 21 | Examples: []framework.RequestExample{ 22 | { 23 | Description: "Gets an account on the tenant0 namespace", 24 | Data: map[string]interface{}{ 25 | formatters.IDLabel: exampleAccount.Address, 26 | }, 27 | Response: successExample, 28 | }, 29 | }, 30 | Responses: map[int][]framework.Response{ 31 | 200: {*successExample}, 32 | 400: {utils.Example400Response()}, 33 | 404: {utils.Example404Response()}, 34 | 500: {utils.Example500Response()}, 35 | }, 36 | } 37 | } 38 | 39 | func (c *controller) getHandler() framework.OperationFunc { 40 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 41 | address := data.Get(formatters.IDLabel).(string) 42 | namespace := formatters.GetRequestNamespace(req) 43 | 44 | ctx = log.Context(ctx, c.logger) 45 | account, err := c.useCases.GetAccount().WithStorage(req.Storage).Execute(ctx, address, namespace) 46 | if err != nil { 47 | return errors.ParseHTTPError(err) 48 | } 49 | 50 | return formatters.FormatAccountResponse(account), nil 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/service/ethereum/import.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewImportOperation() *framework.PathOperation { 14 | exampleAccount := utils.ExampleETHAccount() 15 | successExample := utils.Example200Response() 16 | 17 | return &framework.PathOperation{ 18 | Callback: c.importHandler(), 19 | Summary: "Imports an Ethereum account", 20 | Description: "Imports an Ethereum account given a private key, storing it in the Vault and computing its public key and address", 21 | Examples: []framework.RequestExample{ 22 | { 23 | Description: "Imports an account on the tenant0 namespace", 24 | Data: map[string]interface{}{ 25 | formatters.PrivateKeyLabel: exampleAccount.PrivateKey, 26 | }, 27 | Response: successExample, 28 | }, 29 | }, 30 | Responses: map[int][]framework.Response{ 31 | 200: {*successExample}, 32 | 400: {utils.Example400Response()}, 33 | 500: {utils.Example500Response()}, 34 | }, 35 | } 36 | } 37 | 38 | func (c *controller) importHandler() framework.OperationFunc { 39 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 40 | privateKeyString := data.Get(formatters.PrivateKeyLabel).(string) 41 | namespace := formatters.GetRequestNamespace(req) 42 | 43 | if privateKeyString == "" { 44 | return logical.ErrorResponse("private_key must be provided"), nil 45 | } 46 | 47 | ctx = log.Context(ctx, c.logger) 48 | account, err := c.useCases.CreateAccount().WithStorage(req.Storage).Execute(ctx, namespace, privateKeyString) 49 | if err != nil { 50 | return errors.ParseHTTPError(err) 51 | } 52 | 53 | return formatters.FormatAccountResponse(account), nil 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/service/ethereum/list-namespaces.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | ) 11 | 12 | func (c *controller) NewListNamespacesOperation() *framework.PathOperation { 13 | return &framework.PathOperation{ 14 | Callback: c.listNamespacesHandler(), 15 | Summary: "Gets a list of all Ethereum namespaces", 16 | Description: "Gets a list of all Ethereum namespaces", 17 | Examples: []framework.RequestExample{ 18 | { 19 | Description: "Gets all Ethereum namespaces", 20 | Response: &framework.Response{ 21 | Description: "Success", 22 | Example: logical.ListResponse([]string{"ns1", "ns2"}), 23 | }, 24 | }, 25 | }, 26 | Responses: map[int][]framework.Response{ 27 | 200: {framework.Response{ 28 | Description: "Success", 29 | Example: logical.ListResponse([]string{"ns1", "ns2"}), 30 | }}, 31 | 500: {utils.Example500Response()}, 32 | }, 33 | } 34 | } 35 | 36 | func (c *controller) listNamespacesHandler() framework.OperationFunc { 37 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 38 | ctx = log.Context(ctx, c.logger) 39 | namespaces, err := c.useCases.ListNamespaces().WithStorage(req.Storage).Execute(ctx) 40 | if err != nil { 41 | return errors.ParseHTTPError(err) 42 | } 43 | 44 | return logical.ListResponse(namespaces), nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/service/ethereum/list.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewListOperation() *framework.PathOperation { 14 | return &framework.PathOperation{ 15 | Callback: c.listHandler(), 16 | Summary: "Gets a list of all Ethereum accounts", 17 | Description: "Gets a list of all Ethereum accounts optionally filtered by namespace", 18 | Examples: []framework.RequestExample{ 19 | { 20 | Description: "Gets all Ethereum accounts", 21 | Response: &framework.Response{ 22 | Description: "Success", 23 | Example: logical.ListResponse([]string{utils.ExampleETHAccount().Address}), 24 | }, 25 | }, 26 | }, 27 | Responses: map[int][]framework.Response{ 28 | 200: {framework.Response{ 29 | Description: "Success", 30 | Example: logical.ListResponse([]string{utils.ExampleETHAccount().Address}), 31 | }}, 32 | 500: {utils.Example500Response()}, 33 | }, 34 | } 35 | } 36 | 37 | func (c *controller) listHandler() framework.OperationFunc { 38 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 39 | namespace := formatters.GetRequestNamespace(req) 40 | 41 | ctx = log.Context(ctx, c.logger) 42 | accounts, err := c.useCases.ListAccounts().WithStorage(req.Storage).Execute(ctx, namespace) 43 | if err != nil { 44 | return errors.ParseHTTPError(err) 45 | } 46 | 47 | return logical.ListResponse(accounts), nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/service/ethereum/list_namespaces_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | 8 | "github.com/golang/mock/gomock" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func (s *ethereumCtrlTestSuite) TestEthereumController_ListNamespaces() { 15 | path := s.controller.Paths()[7] 16 | listOperation := path.Operations[logical.ListOperation] 17 | 18 | s.T().Run("should define the correct path", func(t *testing.T) { 19 | assert.Equal(t, "namespaces/ethereum/?", path.Pattern) 20 | assert.NotEmpty(t, listOperation) 21 | }) 22 | 23 | s.T().Run("should define correct properties", func(t *testing.T) { 24 | properties := listOperation.Properties() 25 | 26 | assert.NotEmpty(t, properties.Description) 27 | assert.NotEmpty(t, properties.Summary) 28 | assert.NotEmpty(t, properties.Examples[0].Description) 29 | assert.NotEmpty(t, properties.Examples[0].Response) 30 | assert.NotEmpty(t, properties.Responses[200]) 31 | assert.NotEmpty(t, properties.Responses[500]) 32 | }) 33 | 34 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 35 | expectedList := []string{"ns1/ns2", "_"} 36 | request := &logical.Request{ 37 | Storage: s.storage, 38 | } 39 | 40 | s.listNamespacesUC.EXPECT().Execute(gomock.Any()).Return(expectedList, nil) 41 | 42 | response, err := listOperation.Handler()(s.ctx, request, &framework.FieldData{}) 43 | require.NoError(t, err) 44 | 45 | assert.Equal(t, expectedList, response.Data["keys"]) 46 | }) 47 | 48 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 49 | request := &logical.Request{ 50 | Storage: s.storage, 51 | } 52 | expectedErr := errors.NotFoundError("error") 53 | 54 | s.listNamespacesUC.EXPECT().Execute(gomock.Any()).Return(nil, expectedErr) 55 | 56 | _, err := listOperation.Handler()(s.ctx, request, &framework.FieldData{}) 57 | 58 | assert.Equal(t, err, logical.ErrUnsupportedPath) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /src/service/ethereum/list_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 6 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 7 | "github.com/golang/mock/gomock" 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | "github.com/stretchr/testify/assert" 11 | "testing" 12 | ) 13 | 14 | func (s *ethereumCtrlTestSuite) TestEthereumController_List() { 15 | path := s.controller.Paths()[0] 16 | listOperation := path.Operations[logical.ListOperation] 17 | 18 | s.T().Run("should define the correct path", func(t *testing.T) { 19 | assert.Equal(t, "ethereum/accounts/?", path.Pattern) 20 | assert.NotEmpty(t, listOperation) 21 | }) 22 | 23 | s.T().Run("should define correct properties", func(t *testing.T) { 24 | properties := listOperation.Properties() 25 | 26 | assert.NotEmpty(t, properties.Description) 27 | assert.NotEmpty(t, properties.Summary) 28 | assert.NotEmpty(t, properties.Examples[0].Description) 29 | assert.NotEmpty(t, properties.Examples[0].Response) 30 | assert.NotEmpty(t, properties.Responses[200]) 31 | assert.NotEmpty(t, properties.Responses[500]) 32 | }) 33 | 34 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 35 | account := apputils.FakeETHAccount() 36 | expectedList := []string{account.Address} 37 | request := &logical.Request{ 38 | Storage: s.storage, 39 | Headers: map[string][]string{ 40 | formatters.NamespaceHeader: {account.Namespace}, 41 | }, 42 | } 43 | data := &framework.FieldData{} 44 | 45 | s.listAccountsUC.EXPECT().Execute(gomock.Any(), account.Namespace).Return(expectedList, nil) 46 | 47 | response, err := listOperation.Handler()(s.ctx, request, data) 48 | 49 | assert.NoError(t, err) 50 | assert.Equal(t, expectedList, response.Data["keys"]) 51 | }) 52 | 53 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 54 | request := &logical.Request{ 55 | Storage: s.storage, 56 | } 57 | data := &framework.FieldData{} 58 | expectedErr := errors.NotFoundError("error") 59 | 60 | s.listAccountsUC.EXPECT().Execute(gomock.Any(), "").Return(nil, expectedErr) 61 | 62 | _, err := listOperation.Handler()(s.ctx, request, data) 63 | 64 | assert.Equal(t, err, logical.ErrUnsupportedPath) 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/service/ethereum/sign-eea.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewSignEEATransactionOperation() *framework.PathOperation { 16 | exampleAccount := utils.ExampleETHAccount() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.signEEATransactionHandler(), 20 | Summary: "Signs an EEA private transaction using an existing account", 21 | Description: "Signs an EEA private transaction using ECDSA and the private key of an existing account", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Signs an EEA transaction", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleAccount.Address, 27 | formatters.NonceLabel: 0, 28 | formatters.ToLabel: "0x905B88EFf8Bda1543d4d6f4aA05afef143D27E18", 29 | formatters.ChainIDLabel: "1", 30 | formatters.DataLabel: "0xfeee...", 31 | formatters.PrivateFromLabel: "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=", 32 | formatters.PrivateForLabel: []string{"A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=", "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="}, 33 | }, 34 | Response: utils.Example200ResponseSignature(), 35 | }, 36 | }, 37 | Responses: map[int][]framework.Response{ 38 | 200: {*utils.Example200ResponseSignature()}, 39 | 400: {utils.Example400Response()}, 40 | 404: {utils.Example404Response()}, 41 | 500: {utils.Example500Response()}, 42 | }, 43 | } 44 | } 45 | 46 | func (c *controller) signEEATransactionHandler() framework.OperationFunc { 47 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 48 | address := data.Get(formatters.IDLabel).(string) 49 | chainID := data.Get(formatters.ChainIDLabel).(string) 50 | namespace := formatters.GetRequestNamespace(req) 51 | 52 | if chainID == "" { 53 | return errors.ParseHTTPError(errors2.InvalidFormatError("chainID must be provided")) 54 | } 55 | 56 | tx, privateArgs, err := formatters.FormatSignEEATransactionRequest(data) 57 | if err != nil { 58 | return errors.ParseHTTPError(err) 59 | } 60 | 61 | ctx = log.Context(ctx, c.logger) 62 | signature, err := c.useCases.SignEEATransaction().WithStorage(req.Storage).Execute(ctx, address, namespace, chainID, tx, privateArgs) 63 | if err != nil { 64 | return errors.ParseHTTPError(err) 65 | } 66 | 67 | return formatters.FormatSignatureResponse(signature), nil 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/service/ethereum/sign-quorum-priv.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/hashicorp/vault/sdk/framework" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | func (c *controller) NewSignQuorumPrivateTransactionOperation() *framework.PathOperation { 15 | exampleAccount := utils.ExampleETHAccount() 16 | 17 | return &framework.PathOperation{ 18 | Callback: c.signQuorumPrivateTransactionHandler(), 19 | Summary: "Signs a Quorum private transaction using an existing account", 20 | Description: "Signs a Quorum private transaction using ECDSA and the private key of an existing account", 21 | Examples: []framework.RequestExample{ 22 | { 23 | Description: "Signs a Quorum private transaction", 24 | Data: map[string]interface{}{ 25 | formatters.IDLabel: exampleAccount.Address, 26 | formatters.NonceLabel: 0, 27 | formatters.ToLabel: "0x905B88EFf8Bda1543d4d6f4aA05afef143D27E18", 28 | formatters.DataLabel: "0xfeee...", 29 | formatters.AmountLabel: "0", 30 | formatters.GasPriceLabel: "0", 31 | formatters.GasLimitLabel: 21000, 32 | }, 33 | Response: utils.Example200ResponseSignature(), 34 | }, 35 | }, 36 | Responses: map[int][]framework.Response{ 37 | 200: {*utils.Example200ResponseSignature()}, 38 | 400: {utils.Example400Response()}, 39 | 404: {utils.Example404Response()}, 40 | 500: {utils.Example500Response()}, 41 | }, 42 | } 43 | } 44 | 45 | func (c *controller) signQuorumPrivateTransactionHandler() framework.OperationFunc { 46 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 47 | address := data.Get(formatters.IDLabel).(string) 48 | namespace := formatters.GetRequestNamespace(req) 49 | 50 | tx, err := formatters.FormatSignQuorumPrivateTransactionRequest(data) 51 | if err != nil { 52 | return errors.ParseHTTPError(err) 53 | } 54 | 55 | ctx = log.Context(ctx, c.logger) 56 | signature, err := c.useCases.SignQuorumPrivateTransaction().WithStorage(req.Storage).Execute(ctx, address, namespace, tx) 57 | if err != nil { 58 | return errors.ParseHTTPError(err) 59 | } 60 | 61 | return formatters.FormatSignatureResponse(signature), nil 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/service/ethereum/sign-tx.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewSignTransactionOperation() *framework.PathOperation { 16 | exampleAccount := utils.ExampleETHAccount() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.signTransactionHandler(), 20 | Summary: "Signs an Ethereum transaction using an existing account", 21 | Description: "Signs an Ethereum transaction using ECDSA and the private key of an existing account", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Signs an Ethereum transaction", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleAccount.Address, 27 | formatters.NonceLabel: 0, 28 | formatters.ToLabel: "0x905B88EFf8Bda1543d4d6f4aA05afef143D27E18", 29 | formatters.AmountLabel: "0", 30 | formatters.GasPriceLabel: "0", 31 | formatters.GasLimitLabel: 21000, 32 | formatters.ChainIDLabel: "1", 33 | formatters.DataLabel: "0xfeee...", 34 | }, 35 | Response: utils.Example200ResponseSignature(), 36 | }, 37 | }, 38 | Responses: map[int][]framework.Response{ 39 | 200: {*utils.Example200ResponseSignature()}, 40 | 400: {utils.Example400Response()}, 41 | 404: {utils.Example404Response()}, 42 | 500: {utils.Example500Response()}, 43 | }, 44 | } 45 | } 46 | 47 | func (c *controller) signTransactionHandler() framework.OperationFunc { 48 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 49 | address := data.Get(formatters.IDLabel).(string) 50 | chainID := data.Get(formatters.ChainIDLabel).(string) 51 | namespace := formatters.GetRequestNamespace(req) 52 | 53 | if chainID == "" { 54 | return errors.ParseHTTPError(errors2.InvalidFormatError("chainID must be provided")) 55 | } 56 | 57 | tx, err := formatters.FormatSignETHTransactionRequest(data) 58 | if err != nil { 59 | return errors.ParseHTTPError(err) 60 | } 61 | 62 | ctx = log.Context(ctx, c.logger) 63 | signature, err := c.useCases.SignTransaction().WithStorage(req.Storage).Execute(ctx, address, namespace, chainID, tx) 64 | if err != nil { 65 | return errors.ParseHTTPError(err) 66 | } 67 | 68 | return formatters.FormatSignatureResponse(signature), nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/service/ethereum/sign.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewSignPayloadOperation() *framework.PathOperation { 16 | exampleAccount := utils.ExampleETHAccount() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.signPayloadHandler(), 20 | Summary: "Signs an arbitrary message using an existing Ethereum account", 21 | Description: "Signs an arbitrary message using ECDSA and the private key of an existing Ethereum account", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Signs a message", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleAccount.Address, 27 | formatters.DataLabel: "my data to sign", 28 | }, 29 | Response: utils.Example200ResponseSignature(), 30 | }, 31 | }, 32 | Responses: map[int][]framework.Response{ 33 | 200: {*utils.Example200ResponseSignature()}, 34 | 400: {utils.Example400Response()}, 35 | 404: {utils.Example404Response()}, 36 | 500: {utils.Example500Response()}, 37 | }, 38 | } 39 | } 40 | 41 | func (c *controller) signPayloadHandler() framework.OperationFunc { 42 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 43 | address := data.Get(formatters.IDLabel).(string) 44 | payload := data.Get(formatters.DataLabel).(string) 45 | namespace := formatters.GetRequestNamespace(req) 46 | 47 | if payload == "" { 48 | return errors.ParseHTTPError(errors2.InvalidFormatError("payload must be provided")) 49 | } 50 | 51 | ctx = log.Context(ctx, c.logger) 52 | signature, err := c.useCases.SignPayload().WithStorage(req.Storage).Execute(ctx, address, namespace, payload) 53 | if err != nil { 54 | return errors.ParseHTTPError(err) 55 | } 56 | 57 | return formatters.FormatSignatureResponse(signature), nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/service/formatters/common.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import "github.com/hashicorp/vault/sdk/logical" 4 | 5 | func FormatSignatureResponse(signature string) *logical.Response { 6 | return &logical.Response{ 7 | Data: map[string]interface{}{ 8 | SignatureLabel: signature, 9 | }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/service/formatters/keys.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 5 | "github.com/hashicorp/vault/sdk/logical" 6 | ) 7 | 8 | func FormatKeyResponse(key *entities.Key) *logical.Response { 9 | return &logical.Response{ 10 | Data: map[string]interface{}{ 11 | IDLabel: key.ID, 12 | CurveLabel: key.Curve, 13 | AlgorithmLabel: key.Algorithm, 14 | PublicKeyLabel: key.PublicKey, 15 | NamespaceLabel: key.Namespace, 16 | TagsLabel: key.Tags, 17 | VersionLabel: key.Version, 18 | CreatedAtLabel: key.CreatedAt, 19 | UpdatedAtLabel: key.UpdatedAt, 20 | }, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/service/formatters/migrations.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 5 | "github.com/hashicorp/vault/sdk/logical" 6 | ) 7 | 8 | func FormatMigrationStatusResponse(status *entities.MigrationStatus) *logical.Response { 9 | return &logical.Response{ 10 | Data: map[string]interface{}{ 11 | "status": status.Status, 12 | "error": status.Error, 13 | "startTime": status.StartTime, 14 | "endtime": status.EndTime, 15 | "n": status.N, 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/service/formatters/request.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "github.com/hashicorp/vault/sdk/logical" 5 | ) 6 | 7 | func GetRequestNamespace(req *logical.Request) string { 8 | namespace := "" 9 | 10 | if val, hasVal := req.Headers[NamespaceHeader]; hasVal { 11 | namespace = val[0] 12 | } 13 | 14 | return namespace 15 | } 16 | -------------------------------------------------------------------------------- /src/service/formatters/zk-snarks.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 5 | "github.com/hashicorp/vault/sdk/logical" 6 | ) 7 | 8 | func FormatZksAccountResponse(account *entities.ZksAccount) *logical.Response { 9 | return &logical.Response{ 10 | Data: map[string]interface{}{ 11 | CurveLabel: account.Curve, 12 | AlgorithmLabel: account.Algorithm, 13 | PublicKeyLabel: account.PublicKey, 14 | NamespaceLabel: account.Namespace, 15 | }, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/service/keys/create.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewCreateOperation() *framework.PathOperation { 16 | successExample := utils.Example200KeysResponse() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.createHandler(), 20 | Summary: "Creates a new key pair", 21 | Description: "Creates a new key pair by generating a private key, storing it in the Vault and computing its public key", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Creates a new key pair on the tenant0 namespace", 25 | Response: successExample, 26 | }, 27 | }, 28 | Responses: map[int][]framework.Response{ 29 | 200: {*successExample}, 30 | 500: {utils.Example500Response()}, 31 | }, 32 | } 33 | } 34 | 35 | func (c *controller) createHandler() framework.OperationFunc { 36 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 37 | namespace := formatters.GetRequestNamespace(req) 38 | id := data.Get(formatters.IDLabel).(string) 39 | curve := data.Get(formatters.CurveLabel).(string) 40 | algo := data.Get(formatters.AlgorithmLabel).(string) 41 | tags := data.Get(formatters.TagsLabel).(map[string]string) 42 | 43 | if id == "" { 44 | return errors.ParseHTTPError(errors2.InvalidFormatError("id must be provided")) 45 | } 46 | if curve == "" { 47 | return errors.ParseHTTPError(errors2.InvalidFormatError("curve must be provided")) 48 | } 49 | if algo == "" { 50 | return errors.ParseHTTPError(errors2.InvalidFormatError("signing_algorithm must be provided")) 51 | } 52 | 53 | ctx = log.Context(ctx, c.logger) 54 | key, err := c.useCases.CreateKey().WithStorage(req.Storage).Execute(ctx, namespace, id, algo, curve, "", tags) 55 | if err != nil { 56 | return errors.ParseHTTPError(err) 57 | } 58 | 59 | return formatters.FormatKeyResponse(key), nil 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/service/keys/destroy.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewDestroyOperation() *framework.PathOperation { 14 | return &framework.PathOperation{ 15 | Callback: c.destroyHandler(), 16 | Summary: "Destroys an existing key pair", 17 | Description: "Destroys an existing key pair. The key will not be recoverable after this operation is performed", 18 | Examples: []framework.RequestExample{ 19 | { 20 | Description: "Destroys an existing key pair", 21 | Data: map[string]interface{}{ 22 | formatters.IDLabel: "my-key", 23 | }, 24 | }, 25 | }, 26 | Responses: map[int][]framework.Response{ 27 | 204: {}, 28 | 404: {utils.Example404Response()}, 29 | 500: {utils.Example500Response()}, 30 | }, 31 | } 32 | } 33 | 34 | func (c *controller) destroyHandler() framework.OperationFunc { 35 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 36 | id := data.Get(formatters.IDLabel).(string) 37 | namespace := formatters.GetRequestNamespace(req) 38 | 39 | ctx = log.Context(ctx, c.logger) 40 | err := c.useCases.DestroyKey().WithStorage(req.Storage).Execute(ctx, namespace, id) 41 | if err != nil { 42 | return errors.ParseHTTPError(err) 43 | } 44 | 45 | return &logical.Response{}, nil 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/service/keys/get.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/hashicorp/vault/sdk/framework" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | func (c *controller) NewGetOperation() *framework.PathOperation { 15 | exampleKey := utils.ExampleKey() 16 | successExample := utils.Example200KeyResponse() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.getHandler(), 20 | Summary: "Gets a key pair", 21 | Description: "Gets a key pair stored in the vault at the given id and namespace", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Gets a key pair on the tenant0 namespace", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleKey.ID, 27 | }, 28 | Response: successExample, 29 | }, 30 | }, 31 | Responses: map[int][]framework.Response{ 32 | 200: {*successExample}, 33 | 400: {utils.Example400Response()}, 34 | 404: {utils.Example404Response()}, 35 | 500: {utils.Example500Response()}, 36 | }, 37 | } 38 | } 39 | 40 | func (c *controller) getHandler() framework.OperationFunc { 41 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 42 | id := data.Get(formatters.IDLabel).(string) 43 | namespace := formatters.GetRequestNamespace(req) 44 | 45 | ctx = log.Context(ctx, c.logger) 46 | key, err := c.useCases.GetKey().WithStorage(req.Storage).Execute(ctx, id, namespace) 47 | if err != nil { 48 | return errors.ParseHTTPError(err) 49 | } 50 | 51 | return formatters.FormatKeyResponse(key), nil 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/service/keys/import.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewImportOperation() *framework.PathOperation { 16 | exampleKey := utils.ExampleKey() 17 | successExample := utils.Example200KeyResponse() 18 | 19 | return &framework.PathOperation{ 20 | Callback: c.importHandler(), 21 | Summary: "Imports a key pair", 22 | Description: "Imports a key pair given a private key, storing it in the Vault and computing its public key and address", 23 | Examples: []framework.RequestExample{ 24 | { 25 | Description: "Imports a key pair on the tenant0 namespace", 26 | Data: map[string]interface{}{ 27 | formatters.PrivateKeyLabel: exampleKey.PrivateKey, 28 | formatters.CurveLabel: exampleKey.Curve, 29 | formatters.AlgorithmLabel: exampleKey.Algorithm, 30 | formatters.IDLabel: exampleKey.ID, 31 | formatters.TagsLabel: exampleKey.Tags, 32 | }, 33 | Response: successExample, 34 | }, 35 | }, 36 | Responses: map[int][]framework.Response{ 37 | 200: {*successExample}, 38 | 400: {utils.Example400Response()}, 39 | 500: {utils.Example500Response()}, 40 | }, 41 | } 42 | } 43 | 44 | func (c *controller) importHandler() framework.OperationFunc { 45 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 46 | namespace := formatters.GetRequestNamespace(req) 47 | id := data.Get(formatters.IDLabel).(string) 48 | curve := data.Get(formatters.CurveLabel).(string) 49 | algo := data.Get(formatters.AlgorithmLabel).(string) 50 | tags := data.Get(formatters.TagsLabel).(map[string]string) 51 | privateKeyString := data.Get(formatters.PrivateKeyLabel).(string) 52 | 53 | if id == "" { 54 | return errors.ParseHTTPError(errors2.InvalidFormatError("id must be provided")) 55 | } 56 | if curve == "" { 57 | return errors.ParseHTTPError(errors2.InvalidFormatError("curve must be provided")) 58 | } 59 | if algo == "" { 60 | return errors.ParseHTTPError(errors2.InvalidFormatError("algorithm must be provided")) 61 | } 62 | if privateKeyString == "" { 63 | return errors.ParseHTTPError(errors2.InvalidFormatError("privateKey must be provided")) 64 | } 65 | 66 | ctx = log.Context(ctx, c.logger) 67 | key, err := c.useCases.CreateKey().WithStorage(req.Storage).Execute(ctx, namespace, id, algo, curve, privateKeyString, tags) 68 | if err != nil { 69 | return errors.ParseHTTPError(err) 70 | } 71 | 72 | return formatters.FormatKeyResponse(key), nil 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/service/keys/keys_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 8 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 10 | "github.com/hashicorp/go-hclog" 11 | 12 | "github.com/golang/mock/gomock" 13 | "github.com/stretchr/testify/suite" 14 | ) 15 | 16 | type keysCtrlTestSuite struct { 17 | suite.Suite 18 | createKeyUC *mocks.MockCreateKeyUseCase 19 | getKeyUC *mocks.MockGetKeyUseCase 20 | listKeysUC *mocks.MockListKeysUseCase 21 | listNamespacesUC *mocks.MockListKeysNamespacesUseCase 22 | signPayloadUC *mocks.MockKeysSignUseCase 23 | destroyKeyUC *mocks.MockDestroyKeyUseCase 24 | updateKeyUC *mocks.MockUpdateKeyUseCase 25 | storage *mocks2.MockStorage 26 | ctx context.Context 27 | controller *controller 28 | } 29 | 30 | func (s *keysCtrlTestSuite) CreateKey() usecases.CreateKeyUseCase { 31 | return s.createKeyUC 32 | } 33 | 34 | func (s *keysCtrlTestSuite) GetKey() usecases.GetKeyUseCase { 35 | return s.getKeyUC 36 | } 37 | 38 | func (s *keysCtrlTestSuite) ListKeys() usecases.ListKeysUseCase { 39 | return s.listKeysUC 40 | } 41 | 42 | func (s *keysCtrlTestSuite) ListNamespaces() usecases.ListKeysNamespacesUseCase { 43 | return s.listNamespacesUC 44 | } 45 | 46 | func (s *keysCtrlTestSuite) SignPayload() usecases.KeysSignUseCase { 47 | return s.signPayloadUC 48 | } 49 | 50 | func (s *keysCtrlTestSuite) DestroyKey() usecases.DestroyKeyUseCase { 51 | return s.destroyKeyUC 52 | } 53 | 54 | func (s *keysCtrlTestSuite) UpdateKey() usecases.UpdateKeyUseCase { 55 | return s.updateKeyUC 56 | } 57 | 58 | var _ usecases.KeysUseCases = &keysCtrlTestSuite{} 59 | 60 | func TestKeysController(t *testing.T) { 61 | s := new(keysCtrlTestSuite) 62 | suite.Run(t, s) 63 | } 64 | 65 | func (s *keysCtrlTestSuite) SetupTest() { 66 | ctrl := gomock.NewController(s.T()) 67 | defer ctrl.Finish() 68 | 69 | s.createKeyUC = mocks.NewMockCreateKeyUseCase(ctrl) 70 | s.getKeyUC = mocks.NewMockGetKeyUseCase(ctrl) 71 | s.listKeysUC = mocks.NewMockListKeysUseCase(ctrl) 72 | s.listNamespacesUC = mocks.NewMockListKeysNamespacesUseCase(ctrl) 73 | s.signPayloadUC = mocks.NewMockKeysSignUseCase(ctrl) 74 | s.destroyKeyUC = mocks.NewMockDestroyKeyUseCase(ctrl) 75 | s.updateKeyUC = mocks.NewMockUpdateKeyUseCase(ctrl) 76 | s.controller = NewController(s, hclog.Default()) 77 | s.storage = mocks2.NewMockStorage(ctrl) 78 | s.ctx = context.Background() 79 | 80 | s.createKeyUC.EXPECT().WithStorage(s.storage).Return(s.createKeyUC).AnyTimes() 81 | s.getKeyUC.EXPECT().WithStorage(s.storage).Return(s.getKeyUC).AnyTimes() 82 | s.listKeysUC.EXPECT().WithStorage(s.storage).Return(s.listKeysUC).AnyTimes() 83 | s.listNamespacesUC.EXPECT().WithStorage(s.storage).Return(s.listNamespacesUC).AnyTimes() 84 | s.signPayloadUC.EXPECT().WithStorage(s.storage).Return(s.signPayloadUC).AnyTimes() 85 | s.destroyKeyUC.EXPECT().WithStorage(s.storage).Return(s.destroyKeyUC).AnyTimes() 86 | s.updateKeyUC.EXPECT().WithStorage(s.storage).Return(s.updateKeyUC).AnyTimes() 87 | } 88 | -------------------------------------------------------------------------------- /src/service/keys/list-namespaces.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewListNamespacesOperation() *framework.PathOperation { 14 | return &framework.PathOperation{ 15 | Callback: c.listNamespacesHandler(), 16 | Summary: "Gets a list of all keys namespaces", 17 | Description: "Gets a list of all keys namespaces", 18 | Examples: []framework.RequestExample{ 19 | { 20 | Description: "Gets all keys namespaces", 21 | Response: &framework.Response{ 22 | Description: "Success", 23 | Example: logical.ListResponse([]string{"ns1", "ns2"}), 24 | }, 25 | }, 26 | }, 27 | Responses: map[int][]framework.Response{ 28 | 200: {framework.Response{ 29 | Description: "Success", 30 | Example: logical.ListResponse([]string{"ns1", "ns2"}), 31 | }}, 32 | 500: {utils.Example500Response()}, 33 | }, 34 | } 35 | } 36 | 37 | func (c *controller) listNamespacesHandler() framework.OperationFunc { 38 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 39 | ctx = log.Context(ctx, c.logger) 40 | namespaces, err := c.useCases.ListNamespaces().WithStorage(req.Storage).Execute(ctx) 41 | if err != nil { 42 | return errors.ParseHTTPError(err) 43 | } 44 | 45 | return logical.ListResponse(namespaces), nil 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/service/keys/list.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/hashicorp/vault/sdk/framework" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | func (c *controller) NewListOperation() *framework.PathOperation { 15 | return &framework.PathOperation{ 16 | Callback: c.listHandler(), 17 | Summary: "Gets a list of all key pair ids", 18 | Description: "Gets a list of all key pair ids optionally filtered by namespace", 19 | Examples: []framework.RequestExample{ 20 | { 21 | Description: "Gets all key pair ids", 22 | Response: &framework.Response{ 23 | Description: "Success", 24 | Example: logical.ListResponse([]string{utils.ExampleKey().ID}), 25 | }, 26 | }, 27 | }, 28 | Responses: map[int][]framework.Response{ 29 | 200: {framework.Response{ 30 | Description: "Success", 31 | Example: logical.ListResponse([]string{utils.ExampleKey().ID}), 32 | }}, 33 | 500: {utils.Example500Response()}, 34 | }, 35 | } 36 | } 37 | 38 | func (c *controller) listHandler() framework.OperationFunc { 39 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 40 | namespace := formatters.GetRequestNamespace(req) 41 | 42 | ctx = log.Context(ctx, c.logger) 43 | keys, err := c.useCases.ListKeys().WithStorage(req.Storage).Execute(ctx, namespace) 44 | if err != nil { 45 | return errors.ParseHTTPError(err) 46 | } 47 | 48 | return logical.ListResponse(keys), nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/service/keys/list_namespaces_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | 8 | "github.com/golang/mock/gomock" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func (s *keysCtrlTestSuite) TestKeysController_ListNamespaces() { 15 | path := s.controller.Paths()[4] 16 | listOperation := path.Operations[logical.ListOperation] 17 | 18 | s.T().Run("should define the correct path", func(t *testing.T) { 19 | assert.Equal(t, "namespaces/keys/?", path.Pattern) 20 | assert.NotEmpty(t, listOperation) 21 | }) 22 | 23 | s.T().Run("should define correct properties", func(t *testing.T) { 24 | properties := listOperation.Properties() 25 | 26 | assert.NotEmpty(t, properties.Description) 27 | assert.NotEmpty(t, properties.Summary) 28 | assert.NotEmpty(t, properties.Examples[0].Description) 29 | assert.NotEmpty(t, properties.Examples[0].Response) 30 | assert.NotEmpty(t, properties.Responses[200]) 31 | assert.NotEmpty(t, properties.Responses[500]) 32 | }) 33 | 34 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 35 | expectedList := []string{"ns1/ns2", "_"} 36 | request := &logical.Request{ 37 | Storage: s.storage, 38 | } 39 | 40 | s.listNamespacesUC.EXPECT().Execute(gomock.Any()).Return(expectedList, nil) 41 | 42 | response, err := listOperation.Handler()(s.ctx, request, &framework.FieldData{}) 43 | require.NoError(t, err) 44 | 45 | assert.Equal(t, expectedList, response.Data["keys"]) 46 | }) 47 | 48 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 49 | request := &logical.Request{ 50 | Storage: s.storage, 51 | } 52 | expectedErr := errors.NotFoundError("error") 53 | 54 | s.listNamespacesUC.EXPECT().Execute(gomock.Any()).Return(nil, expectedErr) 55 | 56 | _, err := listOperation.Handler()(s.ctx, request, &framework.FieldData{}) 57 | 58 | assert.Equal(t, err, logical.ErrUnsupportedPath) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /src/service/keys/list_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/golang/mock/gomock" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func (s *keysCtrlTestSuite) TestKeysController_List() { 17 | path := s.controller.Paths()[0] 18 | listOperation := path.Operations[logical.ListOperation] 19 | 20 | s.T().Run("should define the correct path", func(t *testing.T) { 21 | assert.Equal(t, "keys/?", path.Pattern) 22 | assert.NotEmpty(t, listOperation) 23 | }) 24 | 25 | s.T().Run("should define correct properties", func(t *testing.T) { 26 | properties := listOperation.Properties() 27 | 28 | assert.NotEmpty(t, properties.Description) 29 | assert.NotEmpty(t, properties.Summary) 30 | assert.NotEmpty(t, properties.Examples[0].Description) 31 | assert.NotEmpty(t, properties.Examples[0].Response) 32 | assert.NotEmpty(t, properties.Responses[200]) 33 | assert.NotEmpty(t, properties.Responses[500]) 34 | }) 35 | 36 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 37 | key := apputils.FakeKey() 38 | expectedList := []string{key.ID} 39 | request := &logical.Request{ 40 | Storage: s.storage, 41 | Headers: map[string][]string{ 42 | formatters.NamespaceHeader: {key.Namespace}, 43 | }, 44 | } 45 | data := &framework.FieldData{} 46 | 47 | s.listKeysUC.EXPECT().Execute(gomock.Any(), key.Namespace).Return(expectedList, nil) 48 | 49 | response, err := listOperation.Handler()(s.ctx, request, data) 50 | require.NoError(t, err) 51 | 52 | assert.Equal(t, expectedList, response.Data["keys"]) 53 | }) 54 | 55 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 56 | request := &logical.Request{ 57 | Storage: s.storage, 58 | } 59 | data := &framework.FieldData{} 60 | expectedErr := errors.NotFoundError("error") 61 | 62 | s.listKeysUC.EXPECT().Execute(gomock.Any(), "").Return(nil, expectedErr) 63 | 64 | _, err := listOperation.Handler()(s.ctx, request, data) 65 | 66 | assert.Equal(t, err, logical.ErrUnsupportedPath) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/service/keys/sign.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewSignPayloadOperation() *framework.PathOperation { 16 | exampleKey := utils.ExampleKey() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.signPayloadHandler(), 20 | Summary: "Signs an arbitrary message using an existing key pair", 21 | Description: "Signs an arbitrary message using the private key of an existing key pair", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Signs a message", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleKey.PublicKey, 27 | formatters.DataLabel: "my data to sign", 28 | }, 29 | Response: utils.Example200ResponseSignature(), 30 | }, 31 | }, 32 | Responses: map[int][]framework.Response{ 33 | 200: {*utils.Example200ResponseSignature()}, 34 | 400: {utils.Example400Response()}, 35 | 404: {utils.Example404Response()}, 36 | 500: {utils.Example500Response()}, 37 | }, 38 | } 39 | } 40 | 41 | func (c *controller) signPayloadHandler() framework.OperationFunc { 42 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 43 | id := data.Get(formatters.IDLabel).(string) 44 | payload := data.Get(formatters.DataLabel).(string) 45 | namespace := formatters.GetRequestNamespace(req) 46 | 47 | if payload == "" { 48 | return errors.ParseHTTPError(errors2.InvalidFormatError("data must be provided")) 49 | } 50 | 51 | ctx = log.Context(ctx, c.logger) 52 | signature, err := c.useCases.SignPayload().WithStorage(req.Storage).Execute(ctx, id, namespace, payload) 53 | if err != nil { 54 | return errors.ParseHTTPError(err) 55 | } 56 | 57 | return formatters.FormatSignatureResponse(signature), nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/service/keys/update.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/hashicorp/vault/sdk/framework" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | func (c *controller) NewUpdateOperation() *framework.PathOperation { 15 | exampleKey := utils.ExampleKey() 16 | successExample := utils.Example200KeysResponse() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.updateHandler(), 20 | Summary: "Updates the tags of a key pair", 21 | Description: "Updates the tags of a key pair", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Updates the tags of a key pair", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleKey.ID, 27 | formatters.TagsLabel: exampleKey.Tags, 28 | }, 29 | Response: successExample, 30 | }, 31 | }, 32 | Responses: map[int][]framework.Response{ 33 | 200: {*successExample}, 34 | 400: {utils.Example400Response()}, 35 | 404: {utils.Example404Response()}, 36 | 500: {utils.Example500Response()}, 37 | }, 38 | } 39 | } 40 | 41 | func (c *controller) updateHandler() framework.OperationFunc { 42 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 43 | id := data.Get(formatters.IDLabel).(string) 44 | tags := data.Get(formatters.TagsLabel).(map[string]string) 45 | namespace := formatters.GetRequestNamespace(req) 46 | 47 | if tags == nil { 48 | return errors.ParseHTTPError(errors2.InvalidFormatError("tags must be provided")) 49 | } 50 | 51 | ctx = log.Context(ctx, c.logger) 52 | key, err := c.useCases.UpdateKey().WithStorage(req.Storage).Execute(ctx, namespace, id, tags) 53 | if err != nil { 54 | return errors.ParseHTTPError(err) 55 | } 56 | 57 | return formatters.FormatKeyResponse(key), nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/service/migrations/eth-to-keys.go: -------------------------------------------------------------------------------- 1 | package migrations 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewEthereumToKeysOperation() *framework.PathOperation { 14 | return &framework.PathOperation{ 15 | Callback: c.ethToKeysHandler(), 16 | Summary: "Migrates Ethereum accounts to the keys namespace", 17 | Description: "Migrates Ethereum accounts to the keys namespace by copying the data from the ethereum/accounts namespace", 18 | Examples: []framework.RequestExample{ 19 | { 20 | Description: "Migrates the current Ethereum accounts to the keys namespace", 21 | Response: nil, 22 | }, 23 | }, 24 | Responses: map[int][]framework.Response{ 25 | 204: {}, 26 | 500: {utils.Example500Response()}, 27 | }, 28 | } 29 | } 30 | 31 | func (c *controller) NewEthereumToKeysStatusOperation() *framework.PathOperation { 32 | return &framework.PathOperation{ 33 | Callback: c.ethToKeysStatusHandler(), 34 | Summary: "Checks the status of the migration", 35 | Description: "Checks the status of the migration for the given namespace", 36 | Examples: []framework.RequestExample{ 37 | { 38 | Description: "Checks the status of the migration", 39 | Response: nil, 40 | }, 41 | }, 42 | Responses: map[int][]framework.Response{ 43 | 204: {}, 44 | 500: {utils.Example500Response()}, 45 | }, 46 | } 47 | } 48 | 49 | func (c *controller) ethToKeysHandler() framework.OperationFunc { 50 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 51 | sourceNamespace := data.Get(formatters.SourceNamespace).(string) 52 | destinationNamespace := formatters.GetRequestNamespace(req) 53 | 54 | ctx = log.Context(ctx, c.logger) 55 | err := c.useCases.EthereumToKeys().Execute(ctx, req.Storage, sourceNamespace, destinationNamespace) 56 | if err != nil { 57 | return errors.ParseHTTPError(err) 58 | } 59 | 60 | return &logical.Response{}, nil 61 | } 62 | } 63 | 64 | func (c *controller) ethToKeysStatusHandler() framework.OperationFunc { 65 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 66 | sourceNamespace := data.Get(formatters.SourceNamespace).(string) 67 | destinationNamespace := formatters.GetRequestNamespace(req) 68 | 69 | ctx = log.Context(ctx, c.logger) 70 | status, err := c.useCases.EthereumToKeys().Status(ctx, sourceNamespace, destinationNamespace) 71 | if err != nil { 72 | return errors.ParseHTTPError(err) 73 | } 74 | 75 | return formatters.FormatMigrationStatusResponse(status), nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/service/migrations/migrations.go: -------------------------------------------------------------------------------- 1 | package migrations 2 | 3 | import ( 4 | "fmt" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 7 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | ) 11 | 12 | type controller struct { 13 | useCases usecases.MigrationsUseCases 14 | logger log.Logger 15 | } 16 | 17 | func NewController(useCases usecases.MigrationsUseCases, logger log.Logger) *controller { 18 | if logger == nil { 19 | logger = log.Default() 20 | } 21 | 22 | return &controller{ 23 | useCases: useCases, 24 | logger: logger.Named("migrations"), 25 | } 26 | } 27 | 28 | // Paths returns the list of paths 29 | func (c *controller) Paths() []*framework.Path { 30 | return framework.PathAppend( 31 | []*framework.Path{ 32 | c.pathEthereumToKeys(), 33 | c.pathEthereumToKeysStatus(), 34 | }, 35 | ) 36 | } 37 | 38 | func (c *controller) pathEthereumToKeys() *framework.Path { 39 | return &framework.Path{ 40 | Pattern: "migrations/ethereum-to-keys/migrate", 41 | HelpSynopsis: "Migrates the current Ethereum accounts to the keys namespace", 42 | Fields: map[string]*framework.FieldSchema{ 43 | formatters.SourceNamespace: { 44 | Type: framework.TypeString, 45 | Description: "Namespace from which to migrate. Use * for all namespaces", 46 | Required: true, 47 | }, 48 | }, 49 | Operations: map[logical.Operation]framework.OperationHandler{ 50 | logical.CreateOperation: c.NewEthereumToKeysOperation(), 51 | logical.UpdateOperation: c.NewEthereumToKeysOperation(), 52 | }, 53 | } 54 | } 55 | 56 | func (c *controller) pathEthereumToKeysStatus() *framework.Path { 57 | return &framework.Path{ 58 | Pattern: fmt.Sprintf("migrations/ethereum-to-keys/status/%s", framework.OptionalParamRegex(formatters.SourceNamespace)), 59 | HelpSynopsis: "Checks the status of the migration", 60 | Fields: map[string]*framework.FieldSchema{ 61 | formatters.SourceNamespace: { 62 | Type: framework.TypeString, 63 | Description: "Namespace from which to check the status", 64 | Required: true, 65 | }, 66 | }, 67 | Operations: map[logical.Operation]framework.OperationHandler{ 68 | logical.ReadOperation: c.NewEthereumToKeysStatusOperation(), 69 | }, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/service/zk-snarks/create.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/hashicorp/vault/sdk/framework" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | func (c *controller) NewCreateOperation() *framework.PathOperation { 15 | successExample := utils.Example200ZksResponse() 16 | 17 | return &framework.PathOperation{ 18 | Callback: c.createHandler(), 19 | Summary: "Creates a new zk-snarks account", 20 | Description: "Creates a new zk-snarks account by generating a private key, storing it in the Vault and computing its public key and address", 21 | Examples: []framework.RequestExample{ 22 | { 23 | Description: "Creates a new account on the tenant0 namespace", 24 | Response: successExample, 25 | }, 26 | }, 27 | Responses: map[int][]framework.Response{ 28 | 200: {*successExample}, 29 | 500: {utils.Example500Response()}, 30 | }, 31 | } 32 | } 33 | 34 | func (c *controller) createHandler() framework.OperationFunc { 35 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 36 | namespace := formatters.GetRequestNamespace(req) 37 | 38 | ctx = log.Context(ctx, c.logger) 39 | account, err := c.useCases.CreateAccount().WithStorage(req.Storage).Execute(ctx, namespace) 40 | if err != nil { 41 | return errors.ParseHTTPError(err) 42 | } 43 | 44 | return formatters.FormatZksAccountResponse(account), nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/service/zk-snarks/create_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/golang/mock/gomock" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func (s *zksCtrlTestSuite) TestZksController_Create() { 17 | path := s.controller.Paths()[0] 18 | createOperation := path.Operations[logical.CreateOperation] 19 | 20 | s.T().Run("should define the correct path", func(t *testing.T) { 21 | assert.Equal(t, "zk-snarks/accounts/?", path.Pattern) 22 | assert.NotEmpty(t, createOperation) 23 | }) 24 | 25 | s.T().Run("should define correct properties", func(t *testing.T) { 26 | properties := createOperation.Properties() 27 | 28 | assert.NotEmpty(t, properties.Description) 29 | assert.NotEmpty(t, properties.Summary) 30 | assert.NotEmpty(t, properties.Examples[0].Description) 31 | assert.Empty(t, properties.Examples[0].Data) 32 | assert.NotEmpty(t, properties.Examples[0].Response) 33 | assert.NotEmpty(t, properties.Responses[200]) 34 | assert.NotEmpty(t, properties.Responses[500]) 35 | }) 36 | 37 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 38 | account := apputils.FakeZksAccount() 39 | request := &logical.Request{ 40 | Storage: s.storage, 41 | Headers: map[string][]string{ 42 | formatters.NamespaceHeader: {account.Namespace}, 43 | }, 44 | } 45 | 46 | s.createAccountUC.EXPECT().Execute(gomock.Any(), account.Namespace).Return(account, nil) 47 | 48 | response, err := createOperation.Handler()(s.ctx, request, &framework.FieldData{}) 49 | require.NoError(t, err) 50 | 51 | assert.Equal(t, account.PublicKey, response.Data[formatters.PublicKeyLabel]) 52 | assert.Equal(t, account.Namespace, response.Data[formatters.NamespaceLabel]) 53 | assert.Equal(t, account.Algorithm, response.Data[formatters.AlgorithmLabel]) 54 | assert.Equal(t, account.Curve, response.Data[formatters.CurveLabel]) 55 | }) 56 | 57 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 58 | request := &logical.Request{ 59 | Storage: s.storage, 60 | } 61 | expectedErr := errors.NotFoundError("error") 62 | 63 | s.createAccountUC.EXPECT().Execute(gomock.Any(), "").Return(nil, expectedErr) 64 | 65 | _, err := createOperation.Handler()(s.ctx, request, &framework.FieldData{}) 66 | 67 | assert.Equal(t, err, logical.ErrUnsupportedPath) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /src/service/zk-snarks/get.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 10 | "github.com/hashicorp/vault/sdk/framework" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | func (c *controller) NewGetOperation() *framework.PathOperation { 15 | exampleAccount := utils.ExampleZksAccount() 16 | successExample := utils.Example200Response() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.getHandler(), 20 | Summary: "Gets an zk-snarks account", 21 | Description: "Gets an zk-snarks account stored in the vault at the given address and namespace", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Gets an account on the tenant0 namespace", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleAccount.PublicKey, 27 | }, 28 | Response: successExample, 29 | }, 30 | }, 31 | Responses: map[int][]framework.Response{ 32 | 200: {*successExample}, 33 | 400: {utils.Example400Response()}, 34 | 404: {utils.Example404Response()}, 35 | 500: {utils.Example500Response()}, 36 | }, 37 | } 38 | } 39 | 40 | func (c *controller) getHandler() framework.OperationFunc { 41 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 42 | accountID := data.Get(formatters.IDLabel).(string) 43 | namespace := formatters.GetRequestNamespace(req) 44 | 45 | ctx = log.Context(ctx, c.logger) 46 | account, err := c.useCases.GetAccount().WithStorage(req.Storage).Execute(ctx, accountID, namespace) 47 | if err != nil { 48 | return errors.ParseHTTPError(err) 49 | } 50 | 51 | return formatters.FormatZksAccountResponse(account), nil 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/service/zk-snarks/list-namespaces.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | ) 11 | 12 | func (c *controller) NewListNamespacesOperation() *framework.PathOperation { 13 | return &framework.PathOperation{ 14 | Callback: c.listNamespacesHandler(), 15 | Summary: "Gets a list of all zk-snarks namespaces", 16 | Description: "Gets a list of all zk-snarks namespaces", 17 | Examples: []framework.RequestExample{ 18 | { 19 | Description: "Gets all zk-snarks namespaces", 20 | Response: &framework.Response{ 21 | Description: "Success", 22 | Example: logical.ListResponse([]string{"ns1", "ns2"}), 23 | }, 24 | }, 25 | }, 26 | Responses: map[int][]framework.Response{ 27 | 200: {framework.Response{ 28 | Description: "Success", 29 | Example: logical.ListResponse([]string{"ns1", "ns2"}), 30 | }}, 31 | 500: {utils.Example500Response()}, 32 | }, 33 | } 34 | } 35 | 36 | func (c *controller) listNamespacesHandler() framework.OperationFunc { 37 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 38 | ctx = log.Context(ctx, c.logger) 39 | namespaces, err := c.useCases.ListNamespaces().WithStorage(req.Storage).Execute(ctx) 40 | if err != nil { 41 | return errors.ParseHTTPError(err) 42 | } 43 | 44 | return logical.ListResponse(namespaces), nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/service/zk-snarks/list.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | func (c *controller) NewListOperation() *framework.PathOperation { 14 | return &framework.PathOperation{ 15 | Callback: c.listHandler(), 16 | Summary: "Gets a list of all zk-snarks accounts", 17 | Description: "Gets a list of all zk-snarks accounts optionally filtered by namespace", 18 | Examples: []framework.RequestExample{ 19 | { 20 | Description: "Gets all zk-snarks accounts", 21 | Response: &framework.Response{ 22 | Description: "Success", 23 | Example: logical.ListResponse([]string{utils.ExampleETHAccount().Address}), 24 | }, 25 | }, 26 | }, 27 | Responses: map[int][]framework.Response{ 28 | 200: {framework.Response{ 29 | Description: "Success", 30 | Example: logical.ListResponse([]string{utils.ExampleETHAccount().Address}), 31 | }}, 32 | 500: {utils.Example500Response()}, 33 | }, 34 | } 35 | } 36 | 37 | func (c *controller) listHandler() framework.OperationFunc { 38 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 39 | namespace := formatters.GetRequestNamespace(req) 40 | 41 | ctx = log.Context(ctx, c.logger) 42 | accounts, err := c.useCases.ListAccounts().WithStorage(req.Storage).Execute(ctx, namespace) 43 | if err != nil { 44 | return errors.ParseHTTPError(err) 45 | } 46 | 47 | return logical.ListResponse(accounts), nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/service/zk-snarks/list_namespaces_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | 8 | "github.com/golang/mock/gomock" 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func (s *zksCtrlTestSuite) TestZksController_ListNamespaces() { 15 | path := s.controller.Paths()[2] 16 | listOperation := path.Operations[logical.ListOperation] 17 | 18 | s.T().Run("should define the correct path", func(t *testing.T) { 19 | assert.Equal(t, "namespaces/zk-snarks/?", path.Pattern) 20 | assert.NotEmpty(t, listOperation) 21 | }) 22 | 23 | s.T().Run("should define correct properties", func(t *testing.T) { 24 | properties := listOperation.Properties() 25 | 26 | assert.NotEmpty(t, properties.Description) 27 | assert.NotEmpty(t, properties.Summary) 28 | assert.NotEmpty(t, properties.Examples[0].Description) 29 | assert.NotEmpty(t, properties.Examples[0].Response) 30 | assert.NotEmpty(t, properties.Responses[200]) 31 | assert.NotEmpty(t, properties.Responses[500]) 32 | }) 33 | 34 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 35 | expectedList := []string{"ns1/ns2", "_"} 36 | request := &logical.Request{ 37 | Storage: s.storage, 38 | } 39 | 40 | s.listNamespacesUC.EXPECT().Execute(gomock.Any()).Return(expectedList, nil) 41 | 42 | response, err := listOperation.Handler()(s.ctx, request, &framework.FieldData{}) 43 | require.NoError(t, err) 44 | 45 | assert.Equal(t, expectedList, response.Data["keys"]) 46 | }) 47 | 48 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 49 | request := &logical.Request{ 50 | Storage: s.storage, 51 | } 52 | expectedErr := errors.NotFoundError("error") 53 | 54 | s.listNamespacesUC.EXPECT().Execute(gomock.Any()).Return(nil, expectedErr) 55 | 56 | _, err := listOperation.Handler()(s.ctx, request, &framework.FieldData{}) 57 | 58 | assert.Equal(t, err, logical.ErrUnsupportedPath) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /src/service/zk-snarks/list_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 6 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 7 | "github.com/golang/mock/gomock" 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | "testing" 13 | ) 14 | 15 | func (s *zksCtrlTestSuite) TestEthereumController_List() { 16 | path := s.controller.Paths()[0] 17 | listOperation := path.Operations[logical.ListOperation] 18 | 19 | s.T().Run("should define the correct path", func(t *testing.T) { 20 | assert.Equal(t, "zk-snarks/accounts/?", path.Pattern) 21 | assert.NotEmpty(t, listOperation) 22 | }) 23 | 24 | s.T().Run("should define correct properties", func(t *testing.T) { 25 | properties := listOperation.Properties() 26 | 27 | assert.NotEmpty(t, properties.Description) 28 | assert.NotEmpty(t, properties.Summary) 29 | assert.NotEmpty(t, properties.Examples[0].Description) 30 | assert.NotEmpty(t, properties.Examples[0].Response) 31 | assert.NotEmpty(t, properties.Responses[200]) 32 | assert.NotEmpty(t, properties.Responses[500]) 33 | }) 34 | 35 | s.T().Run("handler should execute the correct use case", func(t *testing.T) { 36 | account := apputils.FakeZksAccount() 37 | expectedList := []string{account.PublicKey} 38 | request := &logical.Request{ 39 | Storage: s.storage, 40 | Headers: map[string][]string{ 41 | formatters.NamespaceHeader: {account.Namespace}, 42 | }, 43 | } 44 | data := &framework.FieldData{} 45 | 46 | s.listAccountsUC.EXPECT().Execute(gomock.Any(), account.Namespace).Return(expectedList, nil) 47 | 48 | response, err := listOperation.Handler()(s.ctx, request, data) 49 | require.NoError(t, err) 50 | 51 | assert.Equal(t, expectedList, response.Data["keys"]) 52 | }) 53 | 54 | s.T().Run("should map errors correctly and return the correct http status", func(t *testing.T) { 55 | request := &logical.Request{ 56 | Storage: s.storage, 57 | } 58 | data := &framework.FieldData{} 59 | expectedErr := errors.NotFoundError("error") 60 | 61 | s.listAccountsUC.EXPECT().Execute(gomock.Any(), "").Return(nil, expectedErr) 62 | 63 | _, err := listOperation.Handler()(s.ctx, request, data) 64 | 65 | assert.Equal(t, err, logical.ErrUnsupportedPath) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /src/service/zk-snarks/sign.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | errors2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/errors" 7 | 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/hashicorp/vault/sdk/framework" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | func (c *controller) NewSignPayloadOperation() *framework.PathOperation { 16 | exampleAccount := utils.ExampleZksAccount() 17 | 18 | return &framework.PathOperation{ 19 | Callback: c.signPayloadHandler(), 20 | Summary: "Signs an arbitrary message using an existing zk-snarks account", 21 | Description: "Signs an arbitrary message using EDDSA and the private key of an existing zk-snarks account", 22 | Examples: []framework.RequestExample{ 23 | { 24 | Description: "Signs a message", 25 | Data: map[string]interface{}{ 26 | formatters.IDLabel: exampleAccount.PublicKey, 27 | formatters.DataLabel: "my data to sign", 28 | }, 29 | Response: utils.Example200ResponseSignature(), 30 | }, 31 | }, 32 | Responses: map[int][]framework.Response{ 33 | 200: {*utils.Example200ResponseSignature()}, 34 | 400: {utils.Example400Response()}, 35 | 404: {utils.Example404Response()}, 36 | 500: {utils.Example500Response()}, 37 | }, 38 | } 39 | } 40 | 41 | func (c *controller) signPayloadHandler() framework.OperationFunc { 42 | return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 43 | address := data.Get(formatters.IDLabel).(string) 44 | payload := data.Get(formatters.DataLabel).(string) 45 | namespace := formatters.GetRequestNamespace(req) 46 | 47 | if payload == "" { 48 | return errors.ParseHTTPError(errors2.InvalidFormatError("data must be provided")) 49 | } 50 | 51 | ctx = log.Context(ctx, c.logger) 52 | signature, err := c.useCases.SignPayload().WithStorage(req.Storage).Execute(ctx, address, namespace, payload) 53 | if err != nil { 54 | return errors.ParseHTTPError(err) 55 | } 56 | 57 | return formatters.FormatSignatureResponse(signature), nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/service/zk-snarks/zk-snarks_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 8 | "github.com/hashicorp/go-hclog" 9 | "testing" 10 | 11 | "github.com/golang/mock/gomock" 12 | "github.com/stretchr/testify/suite" 13 | ) 14 | 15 | type zksCtrlTestSuite struct { 16 | suite.Suite 17 | createAccountUC *mocks.MockCreateZksAccountUseCase 18 | getAccountUC *mocks.MockGetZksAccountUseCase 19 | listAccountsUC *mocks.MockListZksAccountsUseCase 20 | listNamespacesUC *mocks.MockListZksNamespacesUseCase 21 | signPayloadUC *mocks.MockZksSignUseCase 22 | storage *mocks2.MockStorage 23 | ctx context.Context 24 | controller *controller 25 | } 26 | 27 | func (s *zksCtrlTestSuite) CreateAccount() usecases.CreateZksAccountUseCase { 28 | return s.createAccountUC 29 | } 30 | 31 | func (s *zksCtrlTestSuite) GetAccount() usecases.GetZksAccountUseCase { 32 | return s.getAccountUC 33 | } 34 | 35 | func (s *zksCtrlTestSuite) ListAccounts() usecases.ListZksAccountsUseCase { 36 | return s.listAccountsUC 37 | } 38 | 39 | func (s *zksCtrlTestSuite) ListNamespaces() usecases.ListZksNamespacesUseCase { 40 | return s.listNamespacesUC 41 | } 42 | 43 | func (s *zksCtrlTestSuite) SignPayload() usecases.ZksSignUseCase { 44 | return s.signPayloadUC 45 | } 46 | 47 | var _ usecases.ZksUseCases = &zksCtrlTestSuite{} 48 | 49 | func TestZksController(t *testing.T) { 50 | s := new(zksCtrlTestSuite) 51 | suite.Run(t, s) 52 | } 53 | 54 | func (s *zksCtrlTestSuite) SetupTest() { 55 | ctrl := gomock.NewController(s.T()) 56 | defer ctrl.Finish() 57 | 58 | s.createAccountUC = mocks.NewMockCreateZksAccountUseCase(ctrl) 59 | s.getAccountUC = mocks.NewMockGetZksAccountUseCase(ctrl) 60 | s.listAccountsUC = mocks.NewMockListZksAccountsUseCase(ctrl) 61 | s.listNamespacesUC = mocks.NewMockListZksNamespacesUseCase(ctrl) 62 | s.signPayloadUC = mocks.NewMockZksSignUseCase(ctrl) 63 | s.controller = NewController(s, hclog.Default()) 64 | s.storage = mocks2.NewMockStorage(ctrl) 65 | s.ctx = context.Background() 66 | 67 | s.createAccountUC.EXPECT().WithStorage(s.storage).Return(s.createAccountUC).AnyTimes() 68 | s.getAccountUC.EXPECT().WithStorage(s.storage).Return(s.getAccountUC).AnyTimes() 69 | s.listAccountsUC.EXPECT().WithStorage(s.storage).Return(s.listAccountsUC).AnyTimes() 70 | s.listNamespacesUC.EXPECT().WithStorage(s.storage).Return(s.listNamespacesUC).AnyTimes() 71 | s.signPayloadUC.EXPECT().WithStorage(s.storage).Return(s.signPayloadUC).AnyTimes() 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/faker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 8 | "github.com/consensys/quorum/common" 9 | ) 10 | 11 | func FakeETHAccount() *entities.ETHAccount { 12 | return &entities.ETHAccount{ 13 | Address: common.HexToAddress(randHexString(12)).String(), 14 | PublicKey: common.HexToHash(randHexString(12)).String(), 15 | CompressedPublicKey: common.HexToHash(randHexString(12)).String(), 16 | Namespace: "_", 17 | } 18 | } 19 | 20 | func FakeZksAccount() *entities.ZksAccount { 21 | return &entities.ZksAccount{ 22 | Algorithm: entities.EDDSA, 23 | Curve: entities.Babyjubjub, 24 | PublicKey: "0x022d15d1be3a2459f4dfdca5b6ec3d255107592c4f231952fcf2b0cb7d77ec05", 25 | PrivateKey: "0x022d15d1be3a2459f4dfdca5b6ec3d255107592c4f231952fcf2b0cb7d77ec05d2751c7f7db7277404b1cd0d83ed1480bef16ac8f502d90283048aa64bb872d6d2795be44a31796693f1084a1f07f9b94c3dcbde35780291fcb0e2e3eeed5c65", 26 | Namespace: "_", 27 | } 28 | } 29 | 30 | func FakeKey() *entities.Key { 31 | return &entities.Key{ 32 | Algorithm: entities.EDDSA, 33 | Curve: entities.Babyjubjub, 34 | PublicKey: "X9Yz_5-O42-eOodHCUBhA4VMD2ZQy5CMAQ6lXqvDUZE=", 35 | PrivateKey: "X9Yz_5-O42-eOodHCUBhA4VMD2ZQy5CMAQ6lXqvDUZGGbioek5qYuzJzTNZpTHrVjjFk7iFe3FYwfpxZyNPxtIaFB5gb9VP9IcHZewwNZly821re7RkmB8pGdjywygPH", 36 | Namespace: "_", 37 | ID: "my-key", 38 | Tags: map[string]string{ 39 | "tag1": "tagValue1", 40 | "tag2": "tagValue2", 41 | }, 42 | Version: 1, 43 | CreatedAt: time.Now(), 44 | UpdatedAt: time.Now(), 45 | } 46 | } 47 | 48 | func randHexString(n int) string { 49 | var letterRunes = []rune("abcdef0123456789") 50 | b := make([]rune, n) 51 | for i := range b { 52 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 53 | } 54 | return string(b) 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/snarks.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func GenerateRandomSeed(n int) []byte { 9 | b := make([]byte, n) 10 | newRand().Read(b) 11 | return b 12 | } 13 | 14 | func newRand() *rand.Rand{ 15 | return rand.New(rand.NewSource(time.Now().UnixNano())) 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/storage.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/vault/sdk/logical" 6 | ) 7 | 8 | //go:generate mockgen -source=storage.go -destination=mocks/storage.go -package=mocks 9 | 10 | // Storage is the way that logical backends are able read/write data. 11 | type Storage interface { 12 | List(context.Context, string) ([]string, error) 13 | Get(context.Context, string) (*logical.StorageEntry, error) 14 | Put(context.Context, *logical.StorageEntry) error 15 | Delete(context.Context, string) error 16 | } 17 | -------------------------------------------------------------------------------- /src/vault.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/migrations" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/ethereum" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/keys" 10 | zksnarks "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/zk-snarks" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/builder" 12 | "github.com/hashicorp/vault/sdk/framework" 13 | "github.com/hashicorp/vault/sdk/logical" 14 | ) 15 | 16 | // NewVaultBackend returns the Hashicorp Vault backend 17 | func NewVaultBackend(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { 18 | vaultPlugin := &framework.Backend{ 19 | Help: "Quorum Hashicorp Vault Plugin", 20 | PathsSpecial: &logical.Paths{ 21 | SealWrapStorage: []string{ 22 | "ethereum/accounts/", 23 | "zk-snarks/accounts/", 24 | "keys/", 25 | "migrations/", 26 | }, 27 | }, 28 | Secrets: []*framework.Secret{}, 29 | BackendType: logical.TypeLogical, 30 | } 31 | 32 | ethUseCases := builder.NewEthereumUseCases() 33 | keysUsecases := builder.NewKeysUseCases() 34 | ethereumController := ethereum.NewController(ethUseCases, conf.Logger) 35 | zkSnarksController := zksnarks.NewController(builder.NewZkSnarksUseCases(), conf.Logger) 36 | keysController := keys.NewController(keysUsecases, conf.Logger) 37 | migrationsController := migrations.NewController(builder.NewMigrationsUseCases(ethUseCases, keysUsecases), conf.Logger) 38 | vaultPlugin.Paths = framework.PathAppend( 39 | ethereumController.Paths(), 40 | zkSnarksController.Paths(), 41 | keysController.Paths(), 42 | migrationsController.Paths(), 43 | ) 44 | 45 | if err := vaultPlugin.Setup(ctx, conf); err != nil { 46 | return nil, err 47 | } 48 | 49 | return vaultPlugin, nil 50 | } 51 | -------------------------------------------------------------------------------- /src/vault/builder/ethereum.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/ethereum" 6 | ) 7 | 8 | type ethereumUseCases struct { 9 | createAccount usecases.CreateAccountUseCase 10 | getAccount usecases.GetAccountUseCase 11 | listAccounts usecases.ListAccountsUseCase 12 | listNamespaces usecases.ListNamespacesUseCase 13 | sign usecases.SignUseCase 14 | signTx usecases.SignTransactionUseCase 15 | signQuorumPrivateTx usecases.SignQuorumPrivateTransactionUseCase 16 | signEEATx usecases.SignEEATransactionUseCase 17 | } 18 | 19 | func NewEthereumUseCases() usecases.ETHUseCases { 20 | getAccount := ethereum.NewGetAccountUseCase() 21 | return ðereumUseCases{ 22 | createAccount: ethereum.NewCreateAccountUseCase(), 23 | getAccount: getAccount, 24 | listAccounts: ethereum.NewListAccountsUseCase(), 25 | listNamespaces: ethereum.NewListNamespacesUseCase(), 26 | sign: ethereum.NewSignUseCase(getAccount), 27 | signTx: ethereum.NewSignTransactionUseCase(getAccount), 28 | signQuorumPrivateTx: ethereum.NewSignQuorumPrivateTransactionUseCase(getAccount), 29 | signEEATx: ethereum.NewSignEEATransactionUseCase(getAccount), 30 | } 31 | } 32 | 33 | func (ucs *ethereumUseCases) CreateAccount() usecases.CreateAccountUseCase { 34 | return ucs.createAccount 35 | } 36 | 37 | func (ucs *ethereumUseCases) GetAccount() usecases.GetAccountUseCase { 38 | return ucs.getAccount 39 | } 40 | 41 | func (ucs *ethereumUseCases) ListAccounts() usecases.ListAccountsUseCase { 42 | return ucs.listAccounts 43 | } 44 | 45 | func (ucs *ethereumUseCases) ListNamespaces() usecases.ListNamespacesUseCase { 46 | return ucs.listNamespaces 47 | } 48 | 49 | func (ucs *ethereumUseCases) SignPayload() usecases.SignUseCase { 50 | return ucs.sign 51 | } 52 | 53 | func (ucs *ethereumUseCases) SignTransaction() usecases.SignTransactionUseCase { 54 | return ucs.signTx 55 | } 56 | 57 | func (ucs *ethereumUseCases) SignQuorumPrivateTransaction() usecases.SignQuorumPrivateTransactionUseCase { 58 | return ucs.signQuorumPrivateTx 59 | } 60 | 61 | func (ucs *ethereumUseCases) SignEEATransaction() usecases.SignEEATransactionUseCase { 62 | return ucs.signEEATx 63 | } 64 | -------------------------------------------------------------------------------- /src/vault/builder/keys.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/keys" 6 | ) 7 | 8 | type keysUseCases struct { 9 | createKey usecases.CreateKeyUseCase 10 | getKey usecases.GetKeyUseCase 11 | listKeys usecases.ListKeysUseCase 12 | listNamespaces usecases.ListKeysNamespacesUseCase 13 | sign usecases.KeysSignUseCase 14 | destroy usecases.DestroyKeyUseCase 15 | update usecases.UpdateKeyUseCase 16 | } 17 | 18 | func NewKeysUseCases() usecases.KeysUseCases { 19 | getKeyUC := keys.NewGetKeyUseCase() 20 | return &keysUseCases{ 21 | createKey: keys.NewCreateKeyUseCase(), 22 | getKey: getKeyUC, 23 | listKeys: keys.NewListKeysUseCase(), 24 | listNamespaces: keys.NewListNamespacesUseCase(), 25 | sign: keys.NewSignUseCase(getKeyUC), 26 | destroy: keys.NewDestroyKeyUseCase(), 27 | update: keys.NewUpdateKeyUseCase(getKeyUC), 28 | } 29 | } 30 | 31 | func (ucs *keysUseCases) CreateKey() usecases.CreateKeyUseCase { 32 | return ucs.createKey 33 | } 34 | 35 | func (ucs *keysUseCases) GetKey() usecases.GetKeyUseCase { 36 | return ucs.getKey 37 | } 38 | 39 | func (ucs *keysUseCases) ListKeys() usecases.ListKeysUseCase { 40 | return ucs.listKeys 41 | } 42 | 43 | func (ucs *keysUseCases) ListNamespaces() usecases.ListKeysNamespacesUseCase { 44 | return ucs.listNamespaces 45 | } 46 | 47 | func (ucs *keysUseCases) SignPayload() usecases.KeysSignUseCase { 48 | return ucs.sign 49 | } 50 | 51 | func (ucs *keysUseCases) DestroyKey() usecases.DestroyKeyUseCase { 52 | return ucs.destroy 53 | } 54 | 55 | func (ucs *keysUseCases) UpdateKey() usecases.UpdateKeyUseCase { 56 | return ucs.update 57 | } 58 | -------------------------------------------------------------------------------- /src/vault/builder/migrations.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/migrations" 6 | ) 7 | 8 | type migrationsUseCases struct { 9 | ethToKeys usecases.EthereumToKeysUseCase 10 | } 11 | 12 | func NewMigrationsUseCases(ethUseCases usecases.ETHUseCases, keysUseCases usecases.KeysUseCases) usecases.MigrationsUseCases { 13 | return &migrationsUseCases{ 14 | ethToKeys: migrations.NewEthToKeysUseCase(ethUseCases, keysUseCases), 15 | } 16 | } 17 | 18 | func (ucs *migrationsUseCases) EthereumToKeys() usecases.EthereumToKeysUseCase { 19 | return ucs.ethToKeys 20 | } 21 | -------------------------------------------------------------------------------- /src/vault/builder/zk-snarks.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/zk-snarks" 6 | ) 7 | 8 | type zkSnarksUseCases struct { 9 | createAccount usecases.CreateZksAccountUseCase 10 | getAccount usecases.GetZksAccountUseCase 11 | listAccounts usecases.ListZksAccountsUseCase 12 | listNamespaces usecases.ListZksNamespacesUseCase 13 | sign usecases.ZksSignUseCase 14 | } 15 | 16 | func NewZkSnarksUseCases() usecases.ZksUseCases { 17 | getAccount := zksnarks.NewGetAccountUseCase() 18 | return &zkSnarksUseCases{ 19 | createAccount: zksnarks.NewCreateAccountUseCase(), 20 | getAccount: getAccount, 21 | listAccounts: zksnarks.NewListAccountsUseCase(), 22 | listNamespaces: zksnarks.NewListNamespacesUseCase(), 23 | sign: zksnarks.NewSignUseCase(getAccount), 24 | } 25 | } 26 | 27 | func (z *zkSnarksUseCases) CreateAccount() usecases.CreateZksAccountUseCase { 28 | return z.createAccount 29 | } 30 | 31 | func (z *zkSnarksUseCases) GetAccount() usecases.GetZksAccountUseCase { 32 | return z.getAccount 33 | } 34 | 35 | func (z *zkSnarksUseCases) ListAccounts() usecases.ListZksAccountsUseCase { 36 | return z.listAccounts 37 | } 38 | 39 | func (z *zkSnarksUseCases) ListNamespaces() usecases.ListZksNamespacesUseCase { 40 | return z.listNamespaces 41 | } 42 | 43 | func (z *zkSnarksUseCases) SignPayload() usecases.ZksSignUseCase { 44 | return z.sign 45 | } 46 | -------------------------------------------------------------------------------- /src/vault/entities/curve.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | const ( 4 | Secp256k1 = "secp256k1" 5 | Babyjubjub = "babyjubjub" 6 | ) 7 | -------------------------------------------------------------------------------- /src/vault/entities/eth_account.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type ETHAccount struct { 4 | Address string `json:"address"` 5 | PrivateKey string `json:"privateKey"` 6 | PublicKey string `json:"publicKey"` 7 | CompressedPublicKey string `json:"compressedPublicKey"` 8 | Namespace string `json:"namespace,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /src/vault/entities/eth_params.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type PrivateTxType string 4 | 5 | const ( 6 | PrivateTxTypeRestricted = "restricted" 7 | ) 8 | 9 | type ETHTransactionParams struct { 10 | From string `json:"from,omitempty" example:"0x1abae27a0cbfb02945720425d3b80c7e09728534"` 11 | To string `json:"to,omitempty" example:"0x1abae27a0cbfb02945720425d3b80c7e09728534"` 12 | Value string `json:"value,omitempty" example:"71500000 (wei)"` 13 | GasPrice string `json:"gasPrice,omitempty" example:"71500000 (wei)"` 14 | Gas string `json:"gas,omitempty" example:"21000"` 15 | MethodSignature string `json:"methodSignature,omitempty" example:"transfer(address,uint256)"` 16 | Args []interface{} `json:"args,omitempty"` 17 | Raw string `json:"raw,omitempty" example:"0xfe378324abcde723"` 18 | ContractName string `json:"contractName,omitempty" example:"MyContract"` 19 | ContractTag string `json:"contractTag,omitempty" example:"v1.1.0"` 20 | Nonce string `json:"nonce,omitempty" example:"1"` 21 | Protocol string `json:"protocol,omitempty" example:"Tessera"` 22 | PrivateFrom string `json:"privateFrom,omitempty" example:"A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="` 23 | PrivateFor []string `json:"privateFor,omitempty" example:"A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=,B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="` 24 | PrivacyGroupID string `json:"privacyGroupId,omitempty" example:"A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="` 25 | } 26 | 27 | type PrivateETHTransactionParams struct { 28 | PrivateFrom string 29 | PrivateFor []string 30 | PrivacyGroupID string 31 | PrivateTxType PrivateTxType 32 | } 33 | -------------------------------------------------------------------------------- /src/vault/entities/key.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type Key struct { 6 | ID string `json:"id"` 7 | Curve string `json:"curve"` 8 | Algorithm string `json:"algorithm"` 9 | PrivateKey string `json:"private_key"` 10 | PublicKey string `json:"public_key"` 11 | Namespace string `json:"namespace,omitempty"` 12 | Tags map[string]string `json:"tags,omitempty"` 13 | CreatedAt time.Time `json:"created_at"` 14 | UpdatedAt time.Time `json:"updated_at"` 15 | Version int `json:"version"` 16 | } 17 | -------------------------------------------------------------------------------- /src/vault/entities/migrations.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type MigrationStatus struct { 6 | Status string `json:"status"` 7 | Error error `json:"error"` 8 | StartTime time.Time `json:"startTime"` 9 | EndTime time.Time `json:"endTime"` 10 | Total int `json:"total"` 11 | N int `json:"n"` 12 | } 13 | -------------------------------------------------------------------------------- /src/vault/entities/signing_algo.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | const ( 4 | EDDSA = "eddsa" 5 | ECDSA = "ecdsa" 6 | ) 7 | -------------------------------------------------------------------------------- /src/vault/entities/testutils/ethereum_faker.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 5 | "github.com/consensys/quorum/common" 6 | ) 7 | 8 | func FakePrivateETHTransactionParams() *entities.PrivateETHTransactionParams { 9 | return &entities.PrivateETHTransactionParams{ 10 | PrivateFrom: "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=", 11 | PrivateFor: []string{"A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="}, 12 | PrivateTxType: entities.PrivateTxTypeRestricted, 13 | } 14 | } 15 | 16 | func FakeETHAccount() *entities.ETHAccount { 17 | return &entities.ETHAccount{ 18 | Namespace: "_", 19 | Address: common.HexToAddress(RandHexString(12)).String(), 20 | PublicKey: common.HexToHash(RandHexString(12)).String(), 21 | CompressedPublicKey: common.HexToHash(RandHexString(12)).String(), 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/vault/entities/testutils/utils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import "math/rand" 4 | 5 | func RandHexString(n int) string { 6 | var letterRunes = []rune("abcdef0123456789") 7 | b := make([]rune, n) 8 | for i := range b { 9 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 10 | } 11 | return string(b) 12 | } 13 | -------------------------------------------------------------------------------- /src/vault/entities/zks_account.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type ZksAccount struct { 4 | Curve string `json:"curve"` 5 | Algorithm string `json:"algorithm"` 6 | PrivateKey string `json:"privateKey"` 7 | PublicKey string `json:"publicKey"` 8 | Namespace string `json:"namespace,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /src/vault/storage/json.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 7 | 8 | "github.com/hashicorp/vault/sdk/logical" 9 | ) 10 | 11 | func StoreJSON(ctx context.Context, storage logical.Storage, key string, data interface{}) error { 12 | logger := log.FromContext(ctx).With("key", key) 13 | 14 | entry, err := logical.StorageEntryJSON(key, data) 15 | if err != nil { 16 | errMessage := "failed to create JSON entry" 17 | logger.With("error", err).Error(errMessage) 18 | return errors.StorageError(errMessage) 19 | } 20 | 21 | err = storage.Put(ctx, entry) 22 | if err != nil { 23 | errMessage := "failed to store entry" 24 | logger.With("error", err).Error(errMessage) 25 | return errors.StorageError(errMessage) 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func GetJSON(ctx context.Context, storage logical.Storage, key string, entity interface{}) error { 32 | logger := log.FromContext(ctx).With("key", key) 33 | 34 | entry, err := storage.Get(ctx, key) 35 | if err != nil { 36 | errMessage := "failed to get resource" 37 | logger.With("error", err).Error(errMessage) 38 | return errors.StorageError(errMessage) 39 | } 40 | 41 | if entry == nil { 42 | errMessage := "resource could not be found" 43 | logger.With("error", err).Error(errMessage) 44 | return errors.NotFoundError(errMessage) 45 | } 46 | 47 | err = entry.DecodeJSON(&entity) 48 | if err != nil { 49 | errMessage := "could not decode entity" 50 | logger.With("error", err).Error(errMessage) 51 | return errors.EncodingError(errMessage) 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func DestroyJSON(ctx context.Context, storage logical.Storage, key string) error { 58 | logger := log.FromContext(ctx).With("key", key) 59 | 60 | err := storage.Delete(ctx, key) 61 | if err != nil { 62 | errMessage := "failed to delete entry" 63 | logger.With("error", err).Error(errMessage) 64 | return errors.StorageError(errMessage) 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /src/vault/storage/namespace.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "strings" 9 | 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | const ( 14 | EthereumSecretsPath = "ethereum" 15 | ZkSnarksSecretsPath = "zk-snarks" 16 | KeysSecretPath = "keys" 17 | ) 18 | 19 | func GetEthereumNamespaces(ctx context.Context, storage logical.Storage, namespace string, namespaceSet map[string]bool) error { 20 | return getNamespaces(ctx, storage, EthereumSecretsPath, namespace, namespaceSet) 21 | } 22 | 23 | func GetZkSnarksNamespaces(ctx context.Context, storage logical.Storage, namespace string, namespaceSet map[string]bool) error { 24 | return getNamespaces(ctx, storage, ZkSnarksSecretsPath, namespace, namespaceSet) 25 | } 26 | 27 | func GetKeysNamespaces(ctx context.Context, storage logical.Storage, namespace string, namespaceSet map[string]bool) error { 28 | return getNamespaces(ctx, storage, KeysSecretPath, namespace, namespaceSet) 29 | } 30 | 31 | func getNamespaces(ctx context.Context, storage logical.Storage, secretsPath, namespace string, namespaceSet map[string]bool) error { 32 | logger := log.FromContext(ctx).With("namespace", namespace) 33 | 34 | if strings.HasSuffix(namespace, secretsPath+"/") { 35 | namespace := strings.TrimSuffix(namespace, secretsPath+"/") 36 | namespaceSet[namespace] = true 37 | return nil 38 | } 39 | 40 | keys, err := storage.List(ctx, namespace) 41 | if err != nil { 42 | errMessage := "failed to get keys" 43 | logger.With("error", err).Error(errMessage) 44 | return errors.StorageError(errMessage) 45 | } 46 | 47 | for _, key := range keys { 48 | err := getNamespaces(ctx, storage, secretsPath, namespace+key, namespaceSet) 49 | if err != nil { 50 | return err 51 | } 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func ComputeEthereumStorageKey(accountID, namespace string) string { 58 | return computeStorageKey(EthereumSecretsPath, accountID, namespace) 59 | } 60 | 61 | func ComputeZksStorageKey(accountID, namespace string) string { 62 | return computeStorageKey(ZkSnarksSecretsPath, accountID, namespace) 63 | } 64 | 65 | func ComputeKeysStorageKey(id, namespace string) string { 66 | path := fmt.Sprintf("%s/%s", KeysSecretPath, id) 67 | if namespace != "" { 68 | path = fmt.Sprintf("%s/%s", namespace, path) 69 | } 70 | 71 | return path 72 | } 73 | 74 | func computeStorageKey(secretsPath, accountID, namespace string) string { 75 | path := fmt.Sprintf("%s/accounts/%s", secretsPath, accountID) 76 | if namespace != "" { 77 | path = fmt.Sprintf("%s/%s", namespace, path) 78 | } 79 | 80 | return path 81 | } 82 | -------------------------------------------------------------------------------- /src/vault/storage/namespace_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestComputeKey(t *testing.T) { 10 | t.Run("should compute key by adding the namespace", func(t *testing.T) { 11 | key := computeStorageKey("ethereum", "address", "namespace") 12 | 13 | assert.Equal(t, "namespace/ethereum/accounts/address", key) 14 | }) 15 | 16 | t.Run("should compute key without namespace", func(t *testing.T) { 17 | key := computeStorageKey("ethereum", "address", "") 18 | 19 | assert.Equal(t, "ethereum/accounts/address", key) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/vault/sdk/logical" 6 | 7 | "github.com/consensys/quorum/core/types" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 10 | ) 11 | 12 | //go:generate mockgen -source=ethereum.go -destination=mocks/ethereum.go -package=mocks 13 | 14 | type ETHUseCases interface { 15 | CreateAccount() CreateAccountUseCase 16 | GetAccount() GetAccountUseCase 17 | ListAccounts() ListAccountsUseCase 18 | ListNamespaces() ListNamespacesUseCase 19 | SignPayload() SignUseCase 20 | SignTransaction() SignTransactionUseCase 21 | SignQuorumPrivateTransaction() SignQuorumPrivateTransactionUseCase 22 | SignEEATransaction() SignEEATransactionUseCase 23 | } 24 | 25 | type CreateAccountUseCase interface { 26 | Execute(ctx context.Context, namespace, importedPrivKey string) (*entities.ETHAccount, error) 27 | WithStorage(storage logical.Storage) CreateAccountUseCase 28 | } 29 | 30 | type GetAccountUseCase interface { 31 | Execute(ctx context.Context, address, namespace string) (*entities.ETHAccount, error) 32 | WithStorage(storage logical.Storage) GetAccountUseCase 33 | } 34 | 35 | type ListAccountsUseCase interface { 36 | Execute(ctx context.Context, namespace string) ([]string, error) 37 | WithStorage(storage logical.Storage) ListAccountsUseCase 38 | } 39 | 40 | type SignUseCase interface { 41 | Execute(ctx context.Context, address, namespace, data string) (string, error) 42 | WithStorage(storage logical.Storage) SignUseCase 43 | } 44 | 45 | type SignTransactionUseCase interface { 46 | Execute(ctx context.Context, address, namespace, chainID string, tx *types.Transaction) (string, error) 47 | WithStorage(storage logical.Storage) SignTransactionUseCase 48 | } 49 | 50 | type SignQuorumPrivateTransactionUseCase interface { 51 | Execute(ctx context.Context, address, namespace string, tx *types.Transaction) (string, error) 52 | WithStorage(storage logical.Storage) SignQuorumPrivateTransactionUseCase 53 | } 54 | 55 | type SignEEATransactionUseCase interface { 56 | Execute( 57 | ctx context.Context, 58 | address, namespace string, chainID string, 59 | tx *types.Transaction, 60 | privateArgs *entities.PrivateETHTransactionParams, 61 | ) (string, error) 62 | WithStorage(storage logical.Storage) SignEEATransactionUseCase 63 | } 64 | 65 | type ListNamespacesUseCase interface { 66 | Execute(ctx context.Context) ([]string, error) 67 | WithStorage(storage logical.Storage) ListNamespacesUseCase 68 | } 69 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/create_account.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "encoding/hex" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 12 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 13 | 14 | "github.com/hashicorp/vault/sdk/logical" 15 | 16 | "github.com/consensys/quorum/common/hexutil" 17 | 18 | "github.com/consensys/quorum/crypto" 19 | ) 20 | 21 | // createAccountUseCase is a use case to create a new Ethereum account 22 | type createAccountUseCase struct { 23 | storage logical.Storage 24 | } 25 | 26 | // NewCreateAccountUseCase creates a new CreateAccountUseCase 27 | func NewCreateAccountUseCase() usecases.CreateAccountUseCase { 28 | return &createAccountUseCase{} 29 | } 30 | 31 | func (uc createAccountUseCase) WithStorage(storage logical.Storage) usecases.CreateAccountUseCase { 32 | uc.storage = storage 33 | return &uc 34 | } 35 | 36 | // Execute creates a new Ethereum account and stores it in the Vault 37 | func (uc *createAccountUseCase) Execute(ctx context.Context, namespace, importedPrivKey string) (*entities.ETHAccount, error) { 38 | logger := log.FromContext(ctx).With("namespace", namespace) 39 | logger.Debug("creating new Ethereum account") 40 | 41 | var privKey = new(ecdsa.PrivateKey) 42 | var err error 43 | if importedPrivKey == "" { 44 | privKey, err = crypto.GenerateKey() 45 | if err != nil { 46 | errMessage := "failed to generate Ethereum private key" 47 | logger.With("error", err).Error(errMessage) 48 | return nil, errors.CryptoOperationError(errMessage) 49 | } 50 | } else { 51 | privKey, err = crypto.HexToECDSA(importedPrivKey) 52 | if err != nil { 53 | errMessage := "failed to import Ethereum private key, please verify that the provided private key is valid" 54 | logger.With("error", err).Error(errMessage) 55 | return nil, errors.InvalidParameterError(errMessage) 56 | } 57 | } 58 | 59 | account := &entities.ETHAccount{ 60 | PrivateKey: hex.EncodeToString(crypto.FromECDSA(privKey)), 61 | Address: crypto.PubkeyToAddress(privKey.PublicKey).Hex(), 62 | PublicKey: hexutil.Encode(crypto.FromECDSAPub(&privKey.PublicKey)), 63 | CompressedPublicKey: hexutil.Encode(crypto.CompressPubkey(&privKey.PublicKey)), 64 | Namespace: namespace, 65 | } 66 | 67 | err = storage.StoreJSON(ctx, uc.storage, storage.ComputeEthereumStorageKey(account.Address, account.Namespace), account) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | logger.With("address", account.Address).Info("Ethereum account created successfully") 73 | return account, nil 74 | } 75 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/create_account_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities/testutils" 8 | "testing" 9 | 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum/common" 13 | 14 | "github.com/golang/mock/gomock" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func TestCreateAccount_Execute(t *testing.T) { 19 | ctrl := gomock.NewController(t) 20 | defer ctrl.Finish() 21 | 22 | mockStorage := mocks.NewMockStorage(ctrl) 23 | ctx := log.Context(context.Background(), log.Default()) 24 | 25 | usecase := NewCreateAccountUseCase().WithStorage(mockStorage) 26 | 27 | t.Run("should execute use case successfully by generating a private key", func(t *testing.T) { 28 | fakeAccount := testutils.FakeETHAccount() 29 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(nil) 30 | 31 | account, err := usecase.Execute(ctx, fakeAccount.Namespace, "") 32 | 33 | assert.NoError(t, err) 34 | assert.Equal(t, fakeAccount.Namespace, account.Namespace) 35 | assert.True(t, common.IsHexAddress(account.Address)) 36 | }) 37 | 38 | t.Run("should execute use case successfully by importing a private key", func(t *testing.T) { 39 | privKey := "fa88c4a5912f80503d6b5503880d0745f4b88a1ff90ce8f64cdd8f32cc3bc249" 40 | 41 | fakeAccount := testutils.FakeETHAccount() 42 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(nil) 43 | 44 | account, err := usecase.Execute(ctx, fakeAccount.Namespace, privKey) 45 | 46 | assert.NoError(t, err) 47 | assert.Equal(t, fakeAccount.Namespace, account.Namespace) 48 | assert.Equal(t, "0xeca84382E0f1dDdE22EedCd0D803442972EC7BE5", account.Address) 49 | }) 50 | 51 | t.Run("should fail with StorageError if Put fails", func(t *testing.T) { 52 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(fmt.Errorf("error")) 53 | 54 | account, err := usecase.Execute(ctx, "namespace", "") 55 | assert.Nil(t, account) 56 | assert.True(t, errors.IsStorageError(err)) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/get_account.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | // getAccountUseCase is a use case to get an Ethereum account 14 | type getAccountUseCase struct { 15 | storage logical.Storage 16 | } 17 | 18 | // NewGetAccountUseCase creates a new GetAccountUseCase 19 | func NewGetAccountUseCase() usecases.GetAccountUseCase { 20 | return &getAccountUseCase{} 21 | } 22 | 23 | func (uc getAccountUseCase) WithStorage(storage logical.Storage) usecases.GetAccountUseCase { 24 | uc.storage = storage 25 | return &uc 26 | } 27 | 28 | // Execute creates a new Ethereum account and stores it in the Vault 29 | func (uc *getAccountUseCase) Execute(ctx context.Context, address, namespace string) (*entities.ETHAccount, error) { 30 | logger := log.FromContext(ctx).With("namespace", namespace).With("address", address) 31 | logger.Debug("getting Ethereum account") 32 | 33 | account := &entities.ETHAccount{} 34 | err := storage.GetJSON(ctx, uc.storage, storage.ComputeEthereumStorageKey(address, namespace), account) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | logger.Debug("Ethereum account found successfully") 40 | return account, nil 41 | } 42 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/get_account_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "github.com/consensys/quorum/common" 8 | "testing" 9 | 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 11 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 13 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 14 | "github.com/hashicorp/vault/sdk/logical" 15 | 16 | "github.com/golang/mock/gomock" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestGetAccount_Execute(t *testing.T) { 21 | ctrl := gomock.NewController(t) 22 | defer ctrl.Finish() 23 | 24 | mockStorage := mocks.NewMockStorage(ctrl) 25 | ctx := log.Context(context.Background(), log.Default()) 26 | 27 | usecase := NewGetAccountUseCase().WithStorage(mockStorage) 28 | 29 | t.Run("should execute use case successfully", func(t *testing.T) { 30 | fakeAccount := apputils.FakeETHAccount() 31 | expectedEntry, _ := logical.StorageEntryJSON(storage.ComputeEthereumStorageKey(fakeAccount.Address, fakeAccount.Namespace), fakeAccount) 32 | mockStorage.EXPECT().Get(ctx, storage.ComputeEthereumStorageKey(fakeAccount.Address, fakeAccount.Namespace)).Return(expectedEntry, nil) 33 | 34 | account, err := usecase.Execute(ctx, fakeAccount.Address, fakeAccount.Namespace) 35 | 36 | assert.NoError(t, err) 37 | assert.Equal(t, fakeAccount.Namespace, account.Namespace) 38 | assert.True(t, common.IsHexAddress(account.Address)) 39 | }) 40 | 41 | t.Run("should fail with StorageError if Get fails", func(t *testing.T) { 42 | mockStorage.EXPECT().Get(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 43 | 44 | account, err := usecase.Execute(ctx, "0xaddress", "namespace") 45 | assert.Nil(t, account) 46 | assert.True(t, errors.IsStorageError(err)) 47 | }) 48 | 49 | t.Run("should fail with NotFoundError if nothing is found", func(t *testing.T) { 50 | mockStorage.EXPECT().Get(ctx, gomock.Any()).Return(nil, nil) 51 | 52 | account, err := usecase.Execute(ctx, "0xaddress", "namespace") 53 | 54 | assert.Nil(t, account) 55 | assert.True(t, errors.IsNotFoundError(err)) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/list_accounts.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | // listAccountsUseCase is a use case to get a list of Ethereum accounts 14 | type listAccountsUseCase struct { 15 | storage logical.Storage 16 | } 17 | 18 | // NewListAccountUseCase creates a new ListAccountsUseCase 19 | func NewListAccountsUseCase() usecases.ListAccountsUseCase { 20 | return &listAccountsUseCase{} 21 | } 22 | 23 | func (uc listAccountsUseCase) WithStorage(storage logical.Storage) usecases.ListAccountsUseCase { 24 | uc.storage = storage 25 | return &uc 26 | } 27 | 28 | // Execute gets a list of Ethereum accounts 29 | func (uc *listAccountsUseCase) Execute(ctx context.Context, namespace string) ([]string, error) { 30 | logger := log.FromContext(ctx).With("namespace", namespace) 31 | logger.Debug("listing Ethereum accounts") 32 | 33 | keys, err := uc.storage.List(ctx, storage.ComputeEthereumStorageKey("", namespace)) 34 | if err != nil { 35 | errMessage := "failed to list keys" 36 | logger.With("error", err).Error(errMessage) 37 | return nil, errors.StorageError(errMessage) 38 | } 39 | 40 | return keys, nil 41 | } 42 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/list_accounts_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 13 | "github.com/golang/mock/gomock" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestListAccounts_Execute(t *testing.T) { 18 | ctrl := gomock.NewController(t) 19 | defer ctrl.Finish() 20 | 21 | mockStorage := mocks.NewMockStorage(ctrl) 22 | ctx := log.Context(context.Background(), log.Default()) 23 | 24 | usecase := NewListAccountsUseCase().WithStorage(mockStorage) 25 | 26 | t.Run("should execute use case successfully", func(t *testing.T) { 27 | fakeAccount := apputils.FakeETHAccount() 28 | expectedKeys := []string{fakeAccount.Address} 29 | mockStorage.EXPECT().List(ctx, storage.ComputeEthereumStorageKey("", fakeAccount.Namespace)).Return(expectedKeys, nil) 30 | 31 | keys, err := usecase.Execute(ctx, fakeAccount.Namespace) 32 | 33 | assert.NoError(t, err) 34 | assert.Equal(t, expectedKeys, keys) 35 | }) 36 | 37 | t.Run("should fail with StorageError if List fails", func(t *testing.T) { 38 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 39 | 40 | keys, err := usecase.Execute(ctx, "namespace") 41 | 42 | assert.Nil(t, keys) 43 | assert.True(t, errors.IsStorageError(err)) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/list_namespaces.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | // listNamespacesUseCase is a use case to get a list of Ethereum accounts 14 | type listNamespacesUseCase struct { 15 | storage logical.Storage 16 | } 17 | 18 | // NewListAccountUseCase creates a new ListAccountsUseCase 19 | func NewListNamespacesUseCase() usecases.ListNamespacesUseCase { 20 | return &listNamespacesUseCase{} 21 | } 22 | 23 | func (uc listNamespacesUseCase) WithStorage(storage logical.Storage) usecases.ListNamespacesUseCase { 24 | uc.storage = storage 25 | return &uc 26 | } 27 | 28 | // Execute get a list of all available namespaces 29 | func (uc *listNamespacesUseCase) Execute(ctx context.Context) ([]string, error) { 30 | logger := log.FromContext(ctx) 31 | logger.Debug("listing ethereum namespaces") 32 | 33 | namespaceSet := make(map[string]bool) 34 | err := storage.GetEthereumNamespaces(ctx, uc.storage, "", namespaceSet) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | namespaces := make([]string, 0, len(namespaceSet)) 40 | for namespace := range namespaceSet { 41 | if namespace != "" { 42 | namespace = strings.TrimSuffix(namespace, "/") 43 | } 44 | namespaces = append(namespaces, namespace) 45 | } 46 | 47 | logger.Debug("ethereum namespaces found successfully") 48 | return namespaces, nil 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/list_namespaces_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 11 | 12 | "github.com/golang/mock/gomock" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestListNamespaces_Execute(t *testing.T) { 17 | ctrl := gomock.NewController(t) 18 | defer ctrl.Finish() 19 | 20 | mockStorage := mocks.NewMockStorage(ctrl) 21 | ctx := log.Context(context.Background(), log.Default()) 22 | 23 | usecase := NewListNamespacesUseCase().WithStorage(mockStorage) 24 | 25 | t.Run("should execute use case successfully", func(t *testing.T) { 26 | // We have 3 namespaces: "ns1/ns2", "_" and "tenant0" 27 | // We also have accounts with no namespace (starting directly with "ethereum/") 28 | expectedNamespaces := []string{"ns1/ns2", "_", "tenant0", ""} 29 | 30 | gomock.InOrder( 31 | mockStorage.EXPECT().List(ctx, "").Return([]string{"ns1/", "_/", "tenant0/", "ethereum/"}, nil), 32 | 33 | // ns1/ns2 with 1 account 34 | mockStorage.EXPECT().List(ctx, "ns1/").Return([]string{"ns2/"}, nil), 35 | mockStorage.EXPECT().List(ctx, "ns1/ns2/").Return([]string{"ethereum/"}, nil), 36 | 37 | // _ with 1 account 38 | mockStorage.EXPECT().List(ctx, "_/").Return([]string{"ethereum/"}, nil), 39 | 40 | // tenant0 with 2 accounts 41 | mockStorage.EXPECT().List(ctx, "tenant0/").Return([]string{"ethereum/", "ethereum/"}, nil), 42 | ) 43 | 44 | namespaces, err := usecase.Execute(ctx) 45 | 46 | assert.NoError(t, err) 47 | assert.Contains(t, namespaces, expectedNamespaces[0]) 48 | assert.Contains(t, namespaces, expectedNamespaces[1]) 49 | assert.Contains(t, namespaces, expectedNamespaces[2]) 50 | assert.Contains(t, namespaces, expectedNamespaces[3]) 51 | }) 52 | 53 | t.Run("should fail with StorageError if List fails", func(t *testing.T) { 54 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 55 | 56 | keys, err := usecase.Execute(ctx) 57 | 58 | assert.Nil(t, keys) 59 | assert.True(t, errors.IsStorageError(err)) 60 | }) 61 | 62 | t.Run("should fail with same error if recursive List fails", func(t *testing.T) { 63 | gomock.InOrder( 64 | mockStorage.EXPECT().List(ctx, "").Return([]string{"ns1/", "_/", "tenant0/", "ethereum/"}, nil), 65 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")), 66 | ) 67 | keys, err := usecase.Execute(ctx) 68 | 69 | assert.Nil(t, keys) 70 | assert.True(t, errors.IsStorageError(err)) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_eea_transaction.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | signing "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/crypto/ethereum" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 11 | "github.com/consensys/quorum/common/hexutil" 12 | "github.com/consensys/quorum/core/types" 13 | "github.com/consensys/quorum/crypto" 14 | "github.com/hashicorp/vault/sdk/logical" 15 | ) 16 | 17 | // signEEATxUseCase is a use case to sign a Quorum private transaction using an existing account 18 | type signEEATxUseCase struct { 19 | getAccountUC usecases.GetAccountUseCase 20 | } 21 | 22 | // NewSignEEATransactionUseCase creates a new signEEATxUseCase 23 | func NewSignEEATransactionUseCase(getAccountUC usecases.GetAccountUseCase) usecases.SignEEATransactionUseCase { 24 | return &signEEATxUseCase{ 25 | getAccountUC: getAccountUC, 26 | } 27 | } 28 | 29 | func (uc signEEATxUseCase) WithStorage(storage logical.Storage) usecases.SignEEATransactionUseCase { 30 | uc.getAccountUC = uc.getAccountUC.WithStorage(storage) 31 | return &uc 32 | } 33 | 34 | // Execute signs an EEA transaction 35 | func (uc *signEEATxUseCase) Execute( 36 | ctx context.Context, 37 | address, namespace, chainID string, 38 | tx *types.Transaction, 39 | privateArgs *entities.PrivateETHTransactionParams, 40 | ) (string, error) { 41 | logger := log.FromContext(ctx).With("namespace", namespace).With("address", address) 42 | logger.Debug("signing eea private transaction") 43 | 44 | account, err := uc.getAccountUC.Execute(ctx, address, namespace) 45 | if err != nil { 46 | return "", err 47 | } 48 | 49 | ecdsaPrivKey, err := crypto.HexToECDSA(account.PrivateKey) 50 | if err != nil { 51 | errMessage := "failed to parse private key" 52 | logger.With("error", err).Error(errMessage) 53 | return "", errors.CryptoOperationError(errMessage) 54 | } 55 | 56 | signature, err := signing.SignEEATransaction(tx, privateArgs, chainID, ecdsaPrivKey) 57 | if err != nil { 58 | errMessage := "failed to sign eea transaction" 59 | logger.With("error", err).Error(errMessage) 60 | return "", errors.CryptoOperationError(errMessage) 61 | } 62 | 63 | logger.Info("eea private transaction signed successfully") 64 | return hexutil.Encode(signature), nil 65 | } 66 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_eea_transaction_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities/testutils" 7 | "math/big" 8 | "testing" 9 | 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 11 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 13 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 14 | "github.com/consensys/quorum/core/types" 15 | "github.com/ethereum/go-ethereum/common" 16 | 17 | "github.com/golang/mock/gomock" 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | func TestSignEEATransaction_Execute(t *testing.T) { 22 | ctrl := gomock.NewController(t) 23 | defer ctrl.Finish() 24 | 25 | mockGetAccountUC := mocks2.NewMockGetAccountUseCase(ctrl) 26 | mockStorage := mocks.NewMockStorage(ctrl) 27 | ctx := log.Context(context.Background(), log.Default()) 28 | address := "0xaddress" 29 | namespace := "namespace" 30 | chainID := "1" 31 | tx := types.NewTransaction( 32 | 0, 33 | common.HexToAddress("0x905B88EFf8Bda1543d4d6f4aA05afef143D27E18"), 34 | big.NewInt(10000000000), 35 | 21000, 36 | big.NewInt(10000000000), 37 | []byte{}, 38 | ) 39 | 40 | mockGetAccountUC.EXPECT().WithStorage(mockStorage).Return(mockGetAccountUC) 41 | usecase := NewSignEEATransactionUseCase(mockGetAccountUC).WithStorage(mockStorage) 42 | 43 | t.Run("should execute use case successfully", func(t *testing.T) { 44 | fakeAccount := apputils.FakeETHAccount() 45 | fakeAccount.PrivateKey = "5385714a2f6d69ca034f56a5268833216ffb8fba7229c39569bc4c5f42cde97c" 46 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(fakeAccount, nil) 47 | privateArgs := testutils.FakePrivateETHTransactionParams() 48 | 49 | signature, err := usecase.Execute(ctx, address, namespace, chainID, tx, privateArgs) 50 | 51 | assert.NoError(t, err) 52 | assert.Equal(t, "0x2424ed4546e2039c9f132222eb361286a485a5e9eade6fc5ee1c9548d5391e146d7e794a3a5aa7b553d3905f65824633aab893985112f1c48e6f51e0a8ceb02001", signature) 53 | }) 54 | 55 | t.Run("should fail with same error if Get account fails", func(t *testing.T) { 56 | privateArgs := testutils.FakePrivateETHTransactionParams() 57 | expectedErr := fmt.Errorf("error") 58 | 59 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(nil, expectedErr) 60 | 61 | signature, err := usecase.Execute(ctx, address, namespace, chainID, tx, privateArgs) 62 | 63 | assert.Empty(t, signature) 64 | assert.Equal(t, expectedErr, err) 65 | }) 66 | 67 | t.Run("should fail with CryptoOperationError if creation of ECDSA private key fails", func(t *testing.T) { 68 | privateArgs := testutils.FakePrivateETHTransactionParams() 69 | fakeAccount := apputils.FakeETHAccount() 70 | fakeAccount.PrivateKey = "invalidPrivKey" 71 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(fakeAccount, nil) 72 | 73 | signature, err := usecase.Execute(ctx, address, namespace, chainID, tx, privateArgs) 74 | 75 | assert.Empty(t, signature) 76 | assert.Error(t, err) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_payload.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | 11 | "github.com/consensys/quorum/common/hexutil" 12 | 13 | "github.com/consensys/quorum/crypto" 14 | ) 15 | 16 | // signPayloadUseCase is a use case to sign an arbitrary payload usign an existing Ethereum account 17 | type signPayloadUseCase struct { 18 | getAccountUC usecases.GetAccountUseCase 19 | } 20 | 21 | // NewSignUseCase creates a new SignUseCase 22 | func NewSignUseCase(getAccountUC usecases.GetAccountUseCase) usecases.SignUseCase { 23 | return &signPayloadUseCase{ 24 | getAccountUC: getAccountUC, 25 | } 26 | } 27 | 28 | func (uc signPayloadUseCase) WithStorage(storage logical.Storage) usecases.SignUseCase { 29 | uc.getAccountUC = uc.getAccountUC.WithStorage(storage) 30 | return &uc 31 | } 32 | 33 | // Execute signs an arbitrary payload using an existing Ethereum account 34 | func (uc *signPayloadUseCase) Execute(ctx context.Context, address, namespace, data string) (string, error) { 35 | logger := log.FromContext(ctx).With("namespace", namespace).With("address", address) 36 | logger.Debug("signing message") 37 | 38 | dataBytes, err := hexutil.Decode(data) 39 | if err != nil { 40 | errMessage := "data must be a hex string" 41 | logger.With("error", err).Error(errMessage) 42 | return "", errors.InvalidParameterError(errMessage) 43 | } 44 | 45 | account, err := uc.getAccountUC.Execute(ctx, address, namespace) 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | ecdsaPrivKey, err := crypto.HexToECDSA(account.PrivateKey) 51 | if err != nil { 52 | errMessage := "failed to parse private key" 53 | logger.With("error", err).Error(errMessage) 54 | return "", errors.CryptoOperationError(errMessage) 55 | } 56 | 57 | signature, err := crypto.Sign(crypto.Keccak256(dataBytes), ecdsaPrivKey) 58 | if err != nil { 59 | errMessage := "failed to sign payload" 60 | logger.With("error", err).Error(errMessage) 61 | return "", errors.CryptoOperationError(errMessage) 62 | } 63 | 64 | logger.Info("payload signed successfully") 65 | return hexutil.Encode(signature), nil 66 | } 67 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_payload_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 13 | "github.com/golang/mock/gomock" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestSignPayload_Execute(t *testing.T) { 18 | ctrl := gomock.NewController(t) 19 | defer ctrl.Finish() 20 | 21 | mockStorage := mocks2.NewMockStorage(ctrl) 22 | mockGetAccountUC := mocks.NewMockGetAccountUseCase(ctrl) 23 | ctx := log.Context(context.Background(), log.Default()) 24 | address := "0xaddress" 25 | namespace := "namespace" 26 | 27 | mockGetAccountUC.EXPECT().WithStorage(mockStorage).Return(mockGetAccountUC).AnyTimes() 28 | 29 | usecase := NewSignUseCase(mockGetAccountUC).WithStorage(mockStorage) 30 | 31 | t.Run("should execute use case successfully", func(t *testing.T) { 32 | account := apputils.FakeETHAccount() 33 | account.PrivateKey = "5385714a2f6d69ca034f56a5268833216ffb8fba7229c39569bc4c5f42cde97c" 34 | 35 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(account, nil) 36 | 37 | signature, err := usecase.Execute(ctx, address, namespace, "0xda") 38 | 39 | assert.NoError(t, err) 40 | assert.Equal(t, "0x618b7f28507e1a1fe180005393ad2e61f6dca806c5bbd7426e3377fc23476a775644aea49fcc0e4a1c61f490979fc7e91043b22c58b441a075255eb88c034bc400", signature) 41 | }) 42 | 43 | t.Run("should fail with same error if Get Account fails", func(t *testing.T) { 44 | expectedErr := fmt.Errorf("error") 45 | 46 | mockGetAccountUC.EXPECT().Execute(ctx, gomock.Any(), gomock.Any()).Return(nil, expectedErr) 47 | 48 | signature, err := usecase.Execute(ctx, address, namespace, "0xda") 49 | 50 | assert.Empty(t, signature) 51 | assert.Equal(t, expectedErr, err) 52 | }) 53 | 54 | t.Run("should fail if creation of ECDSA private key fails", func(t *testing.T) { 55 | account := apputils.FakeETHAccount() 56 | account.PrivateKey = "invalidPrivKey" 57 | 58 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(account, nil) 59 | 60 | signature, err := usecase.Execute(ctx, address, namespace, "0xda") 61 | 62 | assert.Empty(t, signature) 63 | assert.Error(t, err) 64 | }) 65 | 66 | t.Run("should fail with InvalidParameterError if data is not a hex string", func(t *testing.T) { 67 | signature, err := usecase.Execute(ctx, address, namespace, "invalid data") 68 | 69 | assert.Empty(t, signature) 70 | assert.True(t, errors.IsInvalidParameterError(err)) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_quorum_priv_tx.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | signing "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/crypto/ethereum" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/consensys/quorum/crypto" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | 13 | "github.com/consensys/quorum/common/hexutil" 14 | "github.com/consensys/quorum/core/types" 15 | ) 16 | 17 | // signQuorumPrivateTxUseCase is a use case to sign a Quorum private transaction using an existing account 18 | type signQuorumPrivateTxUseCase struct { 19 | getAccountUC usecases.GetAccountUseCase 20 | } 21 | 22 | // NewSignQuorumPrivateTransactionUseCase creates a new signQuorumPrivateTxUseCase 23 | func NewSignQuorumPrivateTransactionUseCase(getAccountUC usecases.GetAccountUseCase) usecases.SignQuorumPrivateTransactionUseCase { 24 | return &signQuorumPrivateTxUseCase{ 25 | getAccountUC: getAccountUC, 26 | } 27 | } 28 | 29 | func (uc signQuorumPrivateTxUseCase) WithStorage(storage logical.Storage) usecases.SignQuorumPrivateTransactionUseCase { 30 | uc.getAccountUC = uc.getAccountUC.WithStorage(storage) 31 | return &uc 32 | } 33 | 34 | // Execute signs a Quorum private transaction 35 | func (uc *signQuorumPrivateTxUseCase) Execute(ctx context.Context, address, namespace string, tx *types.Transaction) (string, error) { 36 | logger := log.FromContext(ctx).With("namespace", namespace).With("address", address) 37 | logger.Debug("signing quorum private transaction") 38 | 39 | account, err := uc.getAccountUC.Execute(ctx, address, namespace) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | ecdsaPrivKey, err := crypto.HexToECDSA(account.PrivateKey) 45 | if err != nil { 46 | errMessage := "failed to parse private key" 47 | logger.With("error", err).Error(errMessage) 48 | return "", errors.CryptoOperationError(errMessage) 49 | } 50 | 51 | signature, err := signing.SignQuorumPrivateTransaction(tx, ecdsaPrivKey, signing.GetQuorumPrivateTxSigner()) 52 | if err != nil { 53 | errMessage := "failed to sign quorum private transaction" 54 | logger.With("error", err).Error(errMessage) 55 | return "", errors.CryptoOperationError(errMessage) 56 | } 57 | 58 | logger.Info("quorum private transaction signed successfully") 59 | return hexutil.Encode(signature), nil 60 | } 61 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_quorum_priv_tx_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 13 | quorumtypes "github.com/consensys/quorum/core/types" 14 | "github.com/ethereum/go-ethereum/common" 15 | 16 | "github.com/golang/mock/gomock" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestSignQuorumPrivateTransaction_Execute(t *testing.T) { 21 | ctrl := gomock.NewController(t) 22 | defer ctrl.Finish() 23 | 24 | mockGetAccountUC := mocks2.NewMockGetAccountUseCase(ctrl) 25 | mockStorage := mocks.NewMockStorage(ctrl) 26 | ctx := log.Context(context.Background(), log.Default()) 27 | address := "0xaddress" 28 | namespace := "namespace" 29 | tx := quorumtypes.NewTransaction( 30 | 0, 31 | common.HexToAddress("0x905B88EFf8Bda1543d4d6f4aA05afef143D27E18"), 32 | big.NewInt(10000000000), 33 | 21000, 34 | big.NewInt(10000000000), 35 | []byte{}, 36 | ) 37 | 38 | mockGetAccountUC.EXPECT().WithStorage(mockStorage).Return(mockGetAccountUC) 39 | usecase := NewSignQuorumPrivateTransactionUseCase(mockGetAccountUC).WithStorage(mockStorage) 40 | 41 | t.Run("should execute use case successfully", func(t *testing.T) { 42 | fakeAccount := apputils.FakeETHAccount() 43 | fakeAccount.PrivateKey = "5385714a2f6d69ca034f56a5268833216ffb8fba7229c39569bc4c5f42cde97c" 44 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(fakeAccount, nil) 45 | 46 | signature, err := usecase.Execute(ctx, address, namespace, tx) 47 | 48 | assert.NoError(t, err) 49 | assert.Equal(t, "0xefa9c4498397ee12e341f6acf81072bbf0c8fb4e4e1813ac96fd3860baa28bb931aecd59811beaffc71a4ef008882d3c13537a2f733be7643fdfea4ea77f3ded00", signature) 50 | }) 51 | 52 | t.Run("should fail with CryptoOperationError if creation of ECDSA private key fails", func(t *testing.T) { 53 | fakeAccount := apputils.FakeETHAccount() 54 | fakeAccount.PrivateKey = "invalidPrivKey" 55 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(fakeAccount, nil) 56 | 57 | signature, err := usecase.Execute(ctx, address, namespace, tx) 58 | 59 | assert.Empty(t, signature) 60 | assert.Error(t, err) 61 | }) 62 | 63 | t.Run("should fail with same error if Get account fails", func(t *testing.T) { 64 | expectedErr := fmt.Errorf("error") 65 | 66 | mockGetAccountUC.EXPECT().Execute(ctx, gomock.Any(), gomock.Any()).Return(nil, expectedErr) 67 | 68 | signature, err := usecase.Execute(ctx, address, namespace, tx) 69 | 70 | assert.Empty(t, signature) 71 | assert.Equal(t, expectedErr, err) 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_transaction.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | signing "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/crypto/ethereum" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "github.com/consensys/quorum/core/types" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 11 | "github.com/consensys/quorum/crypto" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | 14 | "github.com/consensys/quorum/common/hexutil" 15 | ) 16 | 17 | // signTxUseCase is a use case to sign an ethereum transaction using an existing account 18 | type signTxUseCase struct { 19 | getAccountUC usecases.GetAccountUseCase 20 | } 21 | 22 | // NewSignTransactionUseCase creates a new SignTransactionUseCase 23 | func NewSignTransactionUseCase(getAccountUC usecases.GetAccountUseCase) usecases.SignTransactionUseCase { 24 | return &signTxUseCase{ 25 | getAccountUC: getAccountUC, 26 | } 27 | } 28 | 29 | func (uc signTxUseCase) WithStorage(storage logical.Storage) usecases.SignTransactionUseCase { 30 | uc.getAccountUC = uc.getAccountUC.WithStorage(storage) 31 | return &uc 32 | } 33 | 34 | // Execute signs an ethereum transaction 35 | func (uc *signTxUseCase) Execute(ctx context.Context, address, namespace, chainID string, tx *types.Transaction) (string, error) { 36 | logger := log.FromContext(ctx).With("namespace", namespace).With("address", address) 37 | logger.Debug("signing ethereum transaction") 38 | 39 | account, err := uc.getAccountUC.Execute(ctx, address, namespace) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | ecdsaPrivKey, err := crypto.HexToECDSA(account.PrivateKey) 45 | if err != nil { 46 | errMessage := "failed to parse private key" 47 | logger.With("error", err).Error(errMessage) 48 | return "", errors.CryptoOperationError(errMessage) 49 | } 50 | 51 | signature, err := signing.SignTransaction(tx, ecdsaPrivKey, signing.GetEIP155Signer(chainID)) 52 | if err != nil { 53 | errMessage := "failed to sign ethereum transaction" 54 | logger.With("error", err).Error(errMessage) 55 | return "", errors.CryptoOperationError(errMessage) 56 | } 57 | 58 | logger.Info("ethereum transaction signed successfully") 59 | return hexutil.Encode(signature), nil 60 | } 61 | -------------------------------------------------------------------------------- /src/vault/use-cases/ethereum/sign_transaction_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 13 | "github.com/consensys/quorum/core/types" 14 | "github.com/ethereum/go-ethereum/common" 15 | 16 | "github.com/golang/mock/gomock" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestSignTransaction_Execute(t *testing.T) { 21 | ctrl := gomock.NewController(t) 22 | defer ctrl.Finish() 23 | 24 | mockGetAccountUC := mocks.NewMockGetAccountUseCase(ctrl) 25 | mockStorage := mocks2.NewMockStorage(ctrl) 26 | ctx := log.Context(context.Background(), log.Default()) 27 | address := "0xaddress" 28 | namespace := "namespace" 29 | chainID := "1" 30 | tx := types.NewTransaction( 31 | 0, 32 | common.HexToAddress("0x905B88EFf8Bda1543d4d6f4aA05afef143D27E18"), 33 | big.NewInt(10000000000), 34 | 21000, 35 | big.NewInt(10000000000), 36 | []byte{}, 37 | ) 38 | 39 | mockGetAccountUC.EXPECT().WithStorage(mockStorage).Return(mockGetAccountUC) 40 | usecase := NewSignTransactionUseCase(mockGetAccountUC).WithStorage(mockStorage) 41 | 42 | t.Run("should execute use case successfully", func(t *testing.T) { 43 | fakeAccount := apputils.FakeETHAccount() 44 | fakeAccount.PrivateKey = "5385714a2f6d69ca034f56a5268833216ffb8fba7229c39569bc4c5f42cde97c" 45 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(fakeAccount, nil) 46 | 47 | signature, err := usecase.Execute(ctx, address, namespace, chainID, tx) 48 | 49 | assert.NoError(t, err) 50 | assert.Equal(t, "0xd35c752d3498e6f5ca1630d264802a992a141ca4b6a3f439d673c75e944e5fb05278aaa5fabbeac362c321b54e298dedae2d31471e432c26ea36a8d49cf08f1e01", signature) 51 | }) 52 | 53 | t.Run("should fail with CryptoOperationError if creation of ECDSA private key fails", func(t *testing.T) { 54 | fakeAccount := apputils.FakeETHAccount() 55 | fakeAccount.PrivateKey = "invalidPrivKey" 56 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(fakeAccount, nil) 57 | 58 | signature, err := usecase.Execute(ctx, address, namespace, chainID, tx) 59 | 60 | assert.Empty(t, signature) 61 | assert.Error(t, err) 62 | }) 63 | 64 | t.Run("should fail with same error if Get account fails", func(t *testing.T) { 65 | expectedErr := fmt.Errorf("error") 66 | 67 | mockGetAccountUC.EXPECT().Execute(ctx, gomock.Any(), gomock.Any()).Return(nil, expectedErr) 68 | 69 | signature, err := usecase.Execute(ctx, address, namespace, chainID, tx) 70 | 71 | assert.Empty(t, signature) 72 | assert.Equal(t, expectedErr, err) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 7 | "github.com/hashicorp/vault/sdk/logical" 8 | ) 9 | 10 | //go:generate mockgen -source=keys.go -destination=mocks/keys.go -package=mocks 11 | 12 | type KeysUseCases interface { 13 | CreateKey() CreateKeyUseCase 14 | GetKey() GetKeyUseCase 15 | ListKeys() ListKeysUseCase 16 | ListNamespaces() ListKeysNamespacesUseCase 17 | SignPayload() KeysSignUseCase 18 | UpdateKey() UpdateKeyUseCase 19 | DestroyKey() DestroyKeyUseCase 20 | } 21 | 22 | type CreateKeyUseCase interface { 23 | Execute(ctx context.Context, namespace, id, algo, curve, importedPrivKey string, tags map[string]string) (*entities.Key, error) 24 | WithStorage(storage logical.Storage) CreateKeyUseCase 25 | } 26 | 27 | type UpdateKeyUseCase interface { 28 | Execute(ctx context.Context, namespace, id string, tags map[string]string) (*entities.Key, error) 29 | WithStorage(storage logical.Storage) UpdateKeyUseCase 30 | } 31 | 32 | type DestroyKeyUseCase interface { 33 | Execute(ctx context.Context, namespace, id string) error 34 | WithStorage(storage logical.Storage) DestroyKeyUseCase 35 | } 36 | 37 | type GetKeyUseCase interface { 38 | Execute(ctx context.Context, id, namespace string) (*entities.Key, error) 39 | WithStorage(storage logical.Storage) GetKeyUseCase 40 | } 41 | 42 | type ListKeysUseCase interface { 43 | Execute(ctx context.Context, namespace string) ([]string, error) 44 | WithStorage(storage logical.Storage) ListKeysUseCase 45 | } 46 | 47 | type KeysSignUseCase interface { 48 | Execute(ctx context.Context, id, namespace, data string) (string, error) 49 | WithStorage(storage logical.Storage) KeysSignUseCase 50 | } 51 | 52 | type ListKeysNamespacesUseCase interface { 53 | Execute(ctx context.Context) ([]string, error) 54 | WithStorage(storage logical.Storage) ListKeysNamespacesUseCase 55 | } 56 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/destroy_key.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 7 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 8 | "github.com/hashicorp/vault/sdk/logical" 9 | ) 10 | 11 | type destroyKeyUseCase struct { 12 | storage logical.Storage 13 | } 14 | 15 | func NewDestroyKeyUseCase() usecases.DestroyKeyUseCase { 16 | return &destroyKeyUseCase{} 17 | } 18 | 19 | func (uc destroyKeyUseCase) WithStorage(storage logical.Storage) usecases.DestroyKeyUseCase { 20 | uc.storage = storage 21 | return &uc 22 | } 23 | 24 | func (uc *destroyKeyUseCase) Execute(ctx context.Context, namespace, id string) error { 25 | logger := log.FromContext(ctx).With("namespace", namespace).With("id", id) 26 | logger.Debug("permanently deleting key") 27 | 28 | err := storage.DestroyJSON(ctx, uc.storage, storage.ComputeKeysStorageKey(id, namespace)) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | logger.Info("key pair permanently deleted") 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/destroy_key_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/golang/mock/gomock" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestDestroyKey_Execute(t *testing.T) { 17 | ctrl := gomock.NewController(t) 18 | defer ctrl.Finish() 19 | 20 | mockStorage := mocks.NewMockStorage(ctrl) 21 | ctx := log.Context(context.Background(), log.Default()) 22 | 23 | usecase := NewDestroyKeyUseCase().WithStorage(mockStorage) 24 | 25 | t.Run("should execute use case successfully", func(t *testing.T) { 26 | fakeKey := utils.FakeKey() 27 | 28 | mockStorage.EXPECT().Delete(ctx, gomock.Any()).Return(nil) 29 | 30 | err := usecase.Execute(ctx, fakeKey.Namespace, fakeKey.ID) 31 | assert.NoError(t, err) 32 | }) 33 | 34 | t.Run("should fail with StorageError if Delete fails", func(t *testing.T) { 35 | fakeKey := utils.FakeKey() 36 | 37 | mockStorage.EXPECT().Delete(ctx, gomock.Any()).Return(fmt.Errorf("error")) 38 | 39 | err := usecase.Execute(ctx, fakeKey.Namespace, fakeKey.ID) 40 | assert.True(t, errors.IsStorageError(err)) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/get_key.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | type getKeyUseCase struct { 14 | storage logical.Storage 15 | } 16 | 17 | func NewGetKeyUseCase() usecases.GetKeyUseCase { 18 | return &getKeyUseCase{} 19 | } 20 | 21 | func (uc getKeyUseCase) WithStorage(storage logical.Storage) usecases.GetKeyUseCase { 22 | uc.storage = storage 23 | return &uc 24 | } 25 | 26 | func (uc *getKeyUseCase) Execute(ctx context.Context, id, namespace string) (*entities.Key, error) { 27 | logger := log.FromContext(ctx).With("namespace", namespace).With("id", id) 28 | logger.Debug("getting key pair") 29 | 30 | key := &entities.Key{} 31 | err := storage.GetJSON(ctx, uc.storage, storage.ComputeKeysStorageKey(id, namespace), key) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | logger.Debug("key pair found successfully") 37 | return key, nil 38 | } 39 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/get_key_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 13 | "github.com/hashicorp/vault/sdk/logical" 14 | 15 | "github.com/golang/mock/gomock" 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | func TestGetKey_Execute(t *testing.T) { 20 | ctrl := gomock.NewController(t) 21 | defer ctrl.Finish() 22 | 23 | mockStorage := mocks.NewMockStorage(ctrl) 24 | ctx := log.Context(context.Background(), log.Default()) 25 | 26 | usecase := NewGetKeyUseCase().WithStorage(mockStorage) 27 | 28 | t.Run("should execute use case successfully", func(t *testing.T) { 29 | fakeKey := apputils.FakeKey() 30 | expectedEntry, _ := logical.StorageEntryJSON(storage.ComputeKeysStorageKey(fakeKey.ID, fakeKey.Namespace), fakeKey) 31 | mockStorage.EXPECT(). 32 | Get(ctx, storage.ComputeKeysStorageKey(fakeKey.ID, fakeKey.Namespace)). 33 | Return(expectedEntry, nil) 34 | 35 | key, err := usecase.Execute(ctx, fakeKey.ID, fakeKey.Namespace) 36 | 37 | assert.NoError(t, err) 38 | assert.Equal(t, fakeKey.Namespace, key.Namespace) 39 | assert.NotEmpty(t, key.PublicKey) 40 | assert.Equal(t, fakeKey.ID, key.ID) 41 | }) 42 | 43 | t.Run("should fail with StorageError if Get fails", func(t *testing.T) { 44 | mockStorage.EXPECT().Get(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 45 | 46 | key, err := usecase.Execute(ctx, "my-key", "namespace") 47 | assert.Nil(t, key) 48 | assert.True(t, errors.IsStorageError(err)) 49 | }) 50 | 51 | t.Run("should return CodedError with status 404 if nothing is found", func(t *testing.T) { 52 | mockStorage.EXPECT().Get(ctx, gomock.Any()).Return(nil, nil) 53 | 54 | key, err := usecase.Execute(ctx, "my-key", "namespace") 55 | assert.Nil(t, key) 56 | assert.True(t, errors.IsNotFoundError(err)) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/list_keys.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | type listKeysUseCase struct { 14 | storage logical.Storage 15 | } 16 | 17 | func NewListKeysUseCase() usecases.ListKeysUseCase { 18 | return &listKeysUseCase{} 19 | } 20 | 21 | func (uc listKeysUseCase) WithStorage(storage logical.Storage) usecases.ListKeysUseCase { 22 | uc.storage = storage 23 | return &uc 24 | } 25 | 26 | func (uc *listKeysUseCase) Execute(ctx context.Context, namespace string) ([]string, error) { 27 | logger := log.FromContext(ctx).With("namespace", namespace) 28 | logger.Debug("listing key pairs") 29 | 30 | keys, err := uc.storage.List(ctx, storage.ComputeKeysStorageKey("", namespace)) 31 | if err != nil { 32 | errMessage := "failed to list keys" 33 | logger.With("error", err).Error(errMessage) 34 | return nil, errors.StorageError(errMessage) 35 | } 36 | 37 | return keys, nil 38 | } 39 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/list_keys_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 13 | "github.com/golang/mock/gomock" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestListKeys_Execute(t *testing.T) { 18 | ctrl := gomock.NewController(t) 19 | defer ctrl.Finish() 20 | 21 | mockStorage := mocks.NewMockStorage(ctrl) 22 | ctx := log.Context(context.Background(), log.Default()) 23 | 24 | usecase := NewListKeysUseCase().WithStorage(mockStorage) 25 | 26 | t.Run("should execute use case successfully", func(t *testing.T) { 27 | key := apputils.FakeKey() 28 | expectedKeys := []string{key.ID} 29 | mockStorage.EXPECT().List(ctx, storage.ComputeKeysStorageKey("", key.Namespace)).Return(expectedKeys, nil) 30 | 31 | keys, err := usecase.Execute(ctx, key.Namespace) 32 | 33 | assert.NoError(t, err) 34 | assert.Equal(t, expectedKeys, keys) 35 | }) 36 | 37 | t.Run("should fail with StorageError if List fails", func(t *testing.T) { 38 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 39 | 40 | keys, err := usecase.Execute(ctx, "namespace") 41 | assert.Nil(t, keys) 42 | assert.True(t, errors.IsStorageError(err)) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/list_namespaces.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | type listNamespacesUseCase struct { 14 | storage logical.Storage 15 | } 16 | 17 | func NewListNamespacesUseCase() usecases.ListKeysNamespacesUseCase { 18 | return &listNamespacesUseCase{} 19 | } 20 | 21 | func (uc listNamespacesUseCase) WithStorage(storage logical.Storage) usecases.ListKeysNamespacesUseCase { 22 | uc.storage = storage 23 | return &uc 24 | } 25 | 26 | // Execute get a list of all available namespaces 27 | func (uc *listNamespacesUseCase) Execute(ctx context.Context) ([]string, error) { 28 | logger := log.FromContext(ctx) 29 | logger.Debug("listing key pairs namespaces") 30 | 31 | namespaceSet := make(map[string]bool) 32 | err := storage.GetKeysNamespaces(ctx, uc.storage, "", namespaceSet) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | namespaces := make([]string, 0, len(namespaceSet)) 38 | for namespace := range namespaceSet { 39 | if namespace != "" { 40 | namespace = strings.TrimSuffix(namespace, "/") 41 | } 42 | namespaces = append(namespaces, namespace) 43 | } 44 | 45 | logger.Debug("key pairs namespaces found successfully") 46 | return namespaces, nil 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/list_namespaces_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 11 | 12 | "github.com/golang/mock/gomock" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestListNamespaces_Execute(t *testing.T) { 17 | ctrl := gomock.NewController(t) 18 | defer ctrl.Finish() 19 | 20 | mockStorage := mocks.NewMockStorage(ctrl) 21 | ctx := log.Context(context.Background(), log.Default()) 22 | 23 | usecase := NewListNamespacesUseCase().WithStorage(mockStorage) 24 | 25 | t.Run("should execute use case successfully", func(t *testing.T) { 26 | expectedNamespaces := []string{"ns1/ns2", "_", "tenant0", ""} 27 | 28 | gomock.InOrder( 29 | mockStorage.EXPECT().List(ctx, "").Return([]string{"ns1/", "_/", "tenant0/", "keys/"}, nil), 30 | 31 | // ns1/ns2 with 1 account 32 | mockStorage.EXPECT().List(ctx, "ns1/").Return([]string{"ns2/"}, nil), 33 | mockStorage.EXPECT().List(ctx, "ns1/ns2/").Return([]string{"keys/"}, nil), 34 | 35 | // _ with 1 account 36 | mockStorage.EXPECT().List(ctx, "_/").Return([]string{"keys/"}, nil), 37 | 38 | // tenant0 with 2 accounts 39 | mockStorage.EXPECT().List(ctx, "tenant0/").Return([]string{"keys/", "keys/"}, nil), 40 | ) 41 | 42 | namespaces, err := usecase.Execute(ctx) 43 | 44 | assert.NoError(t, err) 45 | assert.Contains(t, namespaces, expectedNamespaces[0]) 46 | assert.Contains(t, namespaces, expectedNamespaces[1]) 47 | assert.Contains(t, namespaces, expectedNamespaces[2]) 48 | assert.Contains(t, namespaces, expectedNamespaces[3]) 49 | }) 50 | 51 | t.Run("should fail with StorageError if List fails", func(t *testing.T) { 52 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 53 | 54 | keys, err := usecase.Execute(ctx) 55 | 56 | assert.Nil(t, keys) 57 | assert.True(t, errors.IsStorageError(err)) 58 | }) 59 | 60 | t.Run("should fail with StorageError if recursive List fails", func(t *testing.T) { 61 | gomock.InOrder( 62 | mockStorage.EXPECT().List(ctx, "").Return([]string{"ns1/", "_/", "tenant0/", "keys/"}, nil), 63 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")), 64 | ) 65 | keys, err := usecase.Execute(ctx) 66 | 67 | assert.Nil(t, keys) 68 | assert.True(t, errors.IsStorageError(err)) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/update_key.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 8 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | ) 11 | 12 | type updateKeyUseCase struct { 13 | storage logical.Storage 14 | getKeyUC usecases.GetKeyUseCase 15 | } 16 | 17 | func NewUpdateKeyUseCase(getKeyUC usecases.GetKeyUseCase) usecases.UpdateKeyUseCase { 18 | return &updateKeyUseCase{ 19 | getKeyUC: getKeyUC, 20 | } 21 | } 22 | 23 | func (uc updateKeyUseCase) WithStorage(storage logical.Storage) usecases.UpdateKeyUseCase { 24 | uc.storage = storage 25 | uc.getKeyUC = uc.getKeyUC.WithStorage(storage) 26 | return &uc 27 | } 28 | 29 | func (uc *updateKeyUseCase) Execute(ctx context.Context, namespace, id string, tags map[string]string) (*entities.Key, error) { 30 | logger := log.FromContext(ctx).With("namespace", namespace).With("id", id) 31 | logger.Debug("updating key") 32 | 33 | key, err := uc.getKeyUC.Execute(ctx, id, namespace) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | key.Tags = tags 39 | err = storage.StoreJSON(ctx, uc.storage, storage.ComputeKeysStorageKey(id, namespace), key) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | logger.Info("key pair updated successfully") 45 | return key, nil 46 | } 47 | -------------------------------------------------------------------------------- /src/vault/use-cases/keys/update_key_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 8 | "testing" 9 | 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 13 | "github.com/golang/mock/gomock" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestUpdateKey_Execute(t *testing.T) { 18 | ctrl := gomock.NewController(t) 19 | defer ctrl.Finish() 20 | 21 | mockStorage := mocks.NewMockStorage(ctrl) 22 | mockGetKey := mocks2.NewMockGetKeyUseCase(ctrl) 23 | ctx := log.Context(context.Background(), log.Default()) 24 | 25 | mockGetKey.EXPECT().WithStorage(mockStorage).Return(mockGetKey).AnyTimes() 26 | 27 | usecase := NewUpdateKeyUseCase(mockGetKey).WithStorage(mockStorage) 28 | 29 | t.Run("should execute use case successfully", func(t *testing.T) { 30 | fakeKey := utils.FakeKey() 31 | newTags := map[string]string{ 32 | "newTag1": "tagValue1", 33 | "newTag2": "tagValue2", 34 | } 35 | 36 | mockGetKey.EXPECT().Execute(ctx, fakeKey.ID, fakeKey.Namespace).Return(fakeKey, nil) 37 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(nil) 38 | 39 | key, err := usecase.Execute(ctx, fakeKey.Namespace, fakeKey.ID, newTags) 40 | 41 | assert.NoError(t, err) 42 | assert.Equal(t, newTags, key.Tags) 43 | }) 44 | 45 | t.Run("should fail with same error if GetKey fails", func(t *testing.T) { 46 | fakeKey := utils.FakeKey() 47 | expectedErr := fmt.Errorf("error") 48 | 49 | mockGetKey.EXPECT().Execute(ctx, fakeKey.ID, fakeKey.Namespace).Return(nil, expectedErr) 50 | 51 | key, err := usecase.Execute(ctx, fakeKey.Namespace, fakeKey.ID, map[string]string{}) 52 | assert.Nil(t, key) 53 | assert.Equal(t, expectedErr, err) 54 | }) 55 | 56 | t.Run("should fail with StorageError if Put fails", func(t *testing.T) { 57 | fakeKey := utils.FakeKey() 58 | 59 | mockGetKey.EXPECT().Execute(ctx, fakeKey.ID, fakeKey.Namespace).Return(fakeKey, nil) 60 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(fmt.Errorf("error")) 61 | 62 | key, err := usecase.Execute(ctx, fakeKey.Namespace, fakeKey.ID, map[string]string{}) 63 | assert.Nil(t, key) 64 | assert.True(t, errors.IsStorageError(err)) 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/vault/use-cases/migrations.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 6 | "github.com/hashicorp/vault/sdk/logical" 7 | ) 8 | 9 | //go:generate mockgen -source=migrations.go -destination=mocks/migrations.go -package=mocks 10 | 11 | type MigrationsUseCases interface { 12 | EthereumToKeys() EthereumToKeysUseCase 13 | } 14 | 15 | type EthereumToKeysUseCase interface { 16 | Execute(ctx context.Context, storage logical.Storage, sourceNamespace, destinationNamespace string) error 17 | Status(ctx context.Context, sourceNamespace, destinationNamespace string) (*entities.MigrationStatus, error) 18 | } 19 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 7 | "github.com/hashicorp/vault/sdk/logical" 8 | ) 9 | 10 | //go:generate mockgen -source=zk-snarks.go -destination=mocks/zk-snarks.go -package=mocks 11 | 12 | type ZksUseCases interface { 13 | CreateAccount() CreateZksAccountUseCase 14 | GetAccount() GetZksAccountUseCase 15 | ListAccounts() ListZksAccountsUseCase 16 | ListNamespaces() ListZksNamespacesUseCase 17 | SignPayload() ZksSignUseCase 18 | } 19 | 20 | type CreateZksAccountUseCase interface { 21 | Execute(ctx context.Context, namespace string) (*entities.ZksAccount, error) 22 | WithStorage(storage logical.Storage) CreateZksAccountUseCase 23 | } 24 | 25 | type GetZksAccountUseCase interface { 26 | Execute(ctx context.Context, pubKey, namespace string) (*entities.ZksAccount, error) 27 | WithStorage(storage logical.Storage) GetZksAccountUseCase 28 | } 29 | 30 | type ListZksAccountsUseCase interface { 31 | Execute(ctx context.Context, namespace string) ([]string, error) 32 | WithStorage(storage logical.Storage) ListZksAccountsUseCase 33 | } 34 | 35 | type ZksSignUseCase interface { 36 | Execute(ctx context.Context, pubKey, namespace, data string) (string, error) 37 | WithStorage(storage logical.Storage) ZksSignUseCase 38 | } 39 | 40 | type ListZksNamespacesUseCase interface { 41 | Execute(ctx context.Context) ([]string, error) 42 | WithStorage(storage logical.Storage) ListZksNamespacesUseCase 43 | } 44 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/create_account.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/crypto" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 11 | usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 12 | "github.com/consensys/quorum/common/hexutil" 13 | "github.com/hashicorp/vault/sdk/logical" 14 | ) 15 | 16 | type createAccountUseCase struct { 17 | storage logical.Storage 18 | } 19 | 20 | func NewCreateAccountUseCase() usecases.CreateZksAccountUseCase { 21 | return &createAccountUseCase{} 22 | } 23 | 24 | func (uc createAccountUseCase) WithStorage(storage logical.Storage) usecases.CreateZksAccountUseCase { 25 | uc.storage = storage 26 | return &uc 27 | } 28 | 29 | func (uc *createAccountUseCase) Execute(ctx context.Context, namespace string) (*entities.ZksAccount, error) { 30 | logger := log.FromContext(ctx).With("namespace", namespace) 31 | logger.Debug("creating new zk-snarks babyjubjub account") 32 | 33 | privKey, err := crypto.NewBabyjubjub() 34 | if err != nil { 35 | errMessage := "failed to generate key" 36 | logger.With("error", err).Error(errMessage) 37 | return nil, errors.CryptoOperationError(errMessage) 38 | } 39 | 40 | account := &entities.ZksAccount{ 41 | Algorithm: entities.EDDSA, 42 | Curve: entities.Babyjubjub, 43 | PrivateKey: hexutil.Encode(privKey.Bytes()), 44 | PublicKey: hexutil.Encode(privKey.Public().Bytes()), 45 | Namespace: namespace, 46 | } 47 | 48 | err = storage.StoreJSON(ctx, uc.storage, storage.ComputeZksStorageKey(account.PublicKey, account.Namespace), account) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | logger.With("pub_key", account.PublicKey).Info("zk-snarks babyjubjub account created successfully") 54 | return account, nil 55 | } 56 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/create_account_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/golang/mock/gomock" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestCreateAccount_Execute(t *testing.T) { 17 | ctrl := gomock.NewController(t) 18 | defer ctrl.Finish() 19 | 20 | mockStorage := mocks.NewMockStorage(ctrl) 21 | ctx := log.Context(context.Background(), log.Default()) 22 | 23 | usecase := NewCreateAccountUseCase().WithStorage(mockStorage) 24 | 25 | t.Run("should execute use case successfully by generating a private key", func(t *testing.T) { 26 | fakeAccount := utils.FakeZksAccount() 27 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(nil) 28 | 29 | account, err := usecase.Execute(ctx, fakeAccount.Namespace) 30 | 31 | assert.NoError(t, err) 32 | assert.Equal(t, fakeAccount.Namespace, account.Namespace) 33 | assert.NotEmpty(t, account.PublicKey) 34 | }) 35 | 36 | t.Run("should fail with StorageError if Put fails", func(t *testing.T) { 37 | mockStorage.EXPECT().Put(ctx, gomock.Any()).Return(fmt.Errorf("error")) 38 | 39 | account, err := usecase.Execute(ctx, "namespace") 40 | assert.Nil(t, account) 41 | assert.True(t, errors.IsStorageError(err)) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/get_account.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | type getAccountUseCase struct { 14 | storage logical.Storage 15 | } 16 | 17 | func NewGetAccountUseCase() usecases.GetZksAccountUseCase { 18 | return &getAccountUseCase{} 19 | } 20 | 21 | func (uc getAccountUseCase) WithStorage(storage logical.Storage) usecases.GetZksAccountUseCase { 22 | uc.storage = storage 23 | return &uc 24 | } 25 | 26 | func (uc *getAccountUseCase) Execute(ctx context.Context, pubKey, namespace string) (*entities.ZksAccount, error) { 27 | logger := log.FromContext(ctx).With("namespace", namespace).With("pub_key", pubKey) 28 | logger.Debug("getting zk-snarks account") 29 | 30 | account := &entities.ZksAccount{} 31 | err := storage.GetJSON(ctx, uc.storage, storage.ComputeZksStorageKey(pubKey, namespace), account) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | logger.Debug("zk-snarks account found successfully") 37 | return account, nil 38 | } 39 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/get_account_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 13 | "github.com/hashicorp/vault/sdk/logical" 14 | 15 | "github.com/golang/mock/gomock" 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | func TestGetAccount_Execute(t *testing.T) { 20 | ctrl := gomock.NewController(t) 21 | defer ctrl.Finish() 22 | 23 | mockStorage := mocks.NewMockStorage(ctrl) 24 | ctx := log.Context(context.Background(), log.Default()) 25 | 26 | usecase := NewGetAccountUseCase().WithStorage(mockStorage) 27 | 28 | t.Run("should execute use case successfully", func(t *testing.T) { 29 | fakeAccount := apputils.FakeZksAccount() 30 | expectedEntry, _ := logical.StorageEntryJSON( 31 | storage.ComputeZksStorageKey(fakeAccount.PublicKey, fakeAccount.Namespace), fakeAccount) 32 | mockStorage.EXPECT(). 33 | Get(ctx, storage.ComputeZksStorageKey(fakeAccount.PublicKey, fakeAccount.Namespace)). 34 | Return(expectedEntry, nil) 35 | 36 | account, err := usecase.Execute(ctx, fakeAccount.PublicKey, fakeAccount.Namespace) 37 | 38 | assert.NoError(t, err) 39 | assert.Equal(t, fakeAccount.Namespace, account.Namespace) 40 | assert.NotEmpty(t, account.PublicKey) 41 | }) 42 | 43 | t.Run("should fail with StorageError if Get fails", func(t *testing.T) { 44 | mockStorage.EXPECT().Get(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 45 | 46 | account, err := usecase.Execute(ctx, "0xaddress", "namespace") 47 | 48 | assert.Nil(t, account) 49 | assert.True(t, errors.IsStorageError(err)) 50 | }) 51 | 52 | t.Run("should fail with NotFoundError if nothing is found", func(t *testing.T) { 53 | mockStorage.EXPECT().Get(ctx, gomock.Any()).Return(nil, nil) 54 | 55 | account, err := usecase.Execute(ctx, "0xaddress", "namespace") 56 | assert.Nil(t, account) 57 | assert.True(t, errors.IsNotFoundError(err)) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/list_accounts.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | type listAccountsUseCase struct { 14 | storage logical.Storage 15 | } 16 | 17 | func NewListAccountsUseCase() usecases.ListZksAccountsUseCase { 18 | return &listAccountsUseCase{} 19 | } 20 | 21 | func (uc listAccountsUseCase) WithStorage(storage logical.Storage) usecases.ListZksAccountsUseCase { 22 | uc.storage = storage 23 | return &uc 24 | } 25 | 26 | // Execute gets a list of Ethereum accounts 27 | func (uc *listAccountsUseCase) Execute(ctx context.Context, namespace string) ([]string, error) { 28 | logger := log.FromContext(ctx).With("namespace", namespace) 29 | logger.Debug("listing zk-snarks accounts") 30 | 31 | keys, err := uc.storage.List(ctx, storage.ComputeZksStorageKey("", namespace)) 32 | if err != nil { 33 | errMessage := "failed to list keys" 34 | logger.With("error", err).Error(errMessage) 35 | return nil, errors.StorageError(errMessage) 36 | } 37 | 38 | return keys, nil 39 | } 40 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/list_accounts_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 11 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 12 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 13 | "github.com/golang/mock/gomock" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestListAccounts_Execute(t *testing.T) { 18 | ctrl := gomock.NewController(t) 19 | defer ctrl.Finish() 20 | 21 | mockStorage := mocks.NewMockStorage(ctrl) 22 | ctx := log.Context(context.Background(), log.Default()) 23 | 24 | usecase := NewListAccountsUseCase().WithStorage(mockStorage) 25 | 26 | t.Run("should execute use case successfully", func(t *testing.T) { 27 | fakeAccount := apputils.FakeETHAccount() 28 | expectedKeys := []string{fakeAccount.Address} 29 | mockStorage.EXPECT().List(ctx, storage.ComputeZksStorageKey("", fakeAccount.Namespace)).Return(expectedKeys, nil) 30 | 31 | keys, err := usecase.Execute(ctx, fakeAccount.Namespace) 32 | 33 | assert.NoError(t, err) 34 | assert.Equal(t, expectedKeys, keys) 35 | }) 36 | 37 | t.Run("should fail with StorageError if List fails", func(t *testing.T) { 38 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 39 | 40 | keys, err := usecase.Execute(ctx, "namespace") 41 | 42 | assert.Nil(t, keys) 43 | assert.True(t, errors.IsStorageError(err)) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/list_namespaces.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 8 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/storage" 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | // listNamespacesUseCase is a use case to get a list of Ethereum accounts 14 | type listNamespacesUseCase struct { 15 | storage logical.Storage 16 | } 17 | 18 | // NewListAccountUseCase creates a new ListAccountsUseCase 19 | func NewListNamespacesUseCase() usecases.ListZksNamespacesUseCase { 20 | return &listNamespacesUseCase{} 21 | } 22 | 23 | func (uc listNamespacesUseCase) WithStorage(storage logical.Storage) usecases.ListZksNamespacesUseCase { 24 | uc.storage = storage 25 | return &uc 26 | } 27 | 28 | // Execute get a list of all available namespaces 29 | func (uc *listNamespacesUseCase) Execute(ctx context.Context) ([]string, error) { 30 | logger := log.FromContext(ctx) 31 | logger.Debug("listing zk-snarks namespaces") 32 | 33 | namespaceSet := make(map[string]bool) 34 | err := storage.GetZkSnarksNamespaces(ctx, uc.storage, "", namespaceSet) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | namespaces := make([]string, 0, len(namespaceSet)) 40 | for namespace := range namespaceSet { 41 | if namespace != "" { 42 | namespace = strings.TrimSuffix(namespace, "/") 43 | } 44 | namespaces = append(namespaces, namespace) 45 | } 46 | 47 | logger.Debug("zk-snarks namespaces found successfully") 48 | return namespaces, nil 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/list_namespaces_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "testing" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 11 | 12 | "github.com/golang/mock/gomock" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestListNamespaces_Execute(t *testing.T) { 17 | ctrl := gomock.NewController(t) 18 | defer ctrl.Finish() 19 | 20 | mockStorage := mocks.NewMockStorage(ctrl) 21 | ctx := log.Context(context.Background(), log.Default()) 22 | 23 | usecase := NewListNamespacesUseCase().WithStorage(mockStorage) 24 | 25 | t.Run("should execute use case successfully", func(t *testing.T) { 26 | expectedNamespaces := []string{"ns1/ns2", "_", "tenant0", ""} 27 | 28 | gomock.InOrder( 29 | mockStorage.EXPECT().List(ctx, "").Return([]string{"ns1/", "_/", "tenant0/", "zk-snarks/"}, nil), 30 | 31 | // ns1/ns2 with 1 account 32 | mockStorage.EXPECT().List(ctx, "ns1/").Return([]string{"ns2/"}, nil), 33 | mockStorage.EXPECT().List(ctx, "ns1/ns2/").Return([]string{"zk-snarks/"}, nil), 34 | 35 | // _ with 1 account 36 | mockStorage.EXPECT().List(ctx, "_/").Return([]string{"zk-snarks/"}, nil), 37 | 38 | // tenant0 with 2 accounts 39 | mockStorage.EXPECT().List(ctx, "tenant0/").Return([]string{"zk-snarks/", "zk-snarks/"}, nil), 40 | ) 41 | 42 | namespaces, err := usecase.Execute(ctx) 43 | 44 | assert.NoError(t, err) 45 | assert.Contains(t, namespaces, expectedNamespaces[0]) 46 | assert.Contains(t, namespaces, expectedNamespaces[1]) 47 | assert.Contains(t, namespaces, expectedNamespaces[2]) 48 | assert.Contains(t, namespaces, expectedNamespaces[3]) 49 | }) 50 | 51 | t.Run("should fail with StorageError if List fails", func(t *testing.T) { 52 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")) 53 | 54 | keys, err := usecase.Execute(ctx) 55 | 56 | assert.Nil(t, keys) 57 | assert.True(t, errors.IsStorageError(err)) 58 | }) 59 | 60 | t.Run("should fail with StorageError if recursive List fails", func(t *testing.T) { 61 | gomock.InOrder( 62 | mockStorage.EXPECT().List(ctx, "").Return([]string{"ns1/", "_/", "tenant0/", "zk-snarks/"}, nil), 63 | mockStorage.EXPECT().List(ctx, gomock.Any()).Return(nil, fmt.Errorf("error")), 64 | ) 65 | keys, err := usecase.Execute(ctx) 66 | 67 | assert.Nil(t, keys) 68 | assert.True(t, errors.IsStorageError(err)) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/sign_payload.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 6 | babyjubjub "github.com/consensys/gnark-crypto/ecc/bn254/twistededwards/eddsa" 7 | "github.com/consensys/gnark-crypto/hash" 8 | 9 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" 11 | "github.com/consensys/quorum/common/hexutil" 12 | "github.com/hashicorp/vault/sdk/logical" 13 | ) 14 | 15 | type signPayloadUseCase struct { 16 | getAccountUC usecases.GetZksAccountUseCase 17 | } 18 | 19 | func NewSignUseCase(getAccountUC usecases.GetZksAccountUseCase) usecases.ZksSignUseCase { 20 | return &signPayloadUseCase{ 21 | getAccountUC: getAccountUC, 22 | } 23 | } 24 | 25 | func (uc signPayloadUseCase) WithStorage(storage logical.Storage) usecases.ZksSignUseCase { 26 | uc.getAccountUC = uc.getAccountUC.WithStorage(storage) 27 | return &uc 28 | } 29 | 30 | func (uc *signPayloadUseCase) Execute(ctx context.Context, pubKey, namespace, data string) (string, error) { 31 | logger := log.FromContext(ctx).With("namespace", namespace).With("pub_key", pubKey) 32 | logger.Debug("signing message") 33 | 34 | dataBytes, err := hexutil.Decode(data) 35 | if err != nil { 36 | errMessage := "data must be a hex string" 37 | logger.With("error", err).Error(errMessage) 38 | return "", errors.InvalidParameterError(errMessage) 39 | } 40 | 41 | account, err := uc.getAccountUC.Execute(ctx, pubKey, namespace) 42 | if err != nil { 43 | return "", err 44 | } 45 | 46 | privKey := babyjubjub.PrivateKey{} 47 | privKeyB, _ := hexutil.Decode(account.PrivateKey) 48 | _, err = privKey.SetBytes(privKeyB) 49 | if err != nil { 50 | errMessage := "failed to parse private key" 51 | logger.With("error", err).Error(errMessage) 52 | return "", errors.CryptoOperationError(errMessage) 53 | } 54 | 55 | signatureB, err := privKey.Sign(dataBytes, hash.MIMC_BN254.New("seed")) 56 | if err != nil { 57 | errMessage := "failed to sign payload" 58 | logger.With("error", err).Error(errMessage) 59 | return "", errors.CryptoOperationError(errMessage) 60 | } 61 | 62 | logger.Info("payload signed successfully") 63 | return hexutil.Encode(signatureB), nil 64 | } 65 | -------------------------------------------------------------------------------- /src/vault/use-cases/zk-snarks/sign_payload_test.go: -------------------------------------------------------------------------------- 1 | package zksnarks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/errors" 7 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/entities" 8 | "testing" 9 | 10 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" 11 | apputils "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils" 12 | mocks2 "github.com/consensys/quorum-hashicorp-vault-plugin/src/utils/mocks" 13 | "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases/mocks" 14 | "github.com/golang/mock/gomock" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func TestSignPayload_Execute(t *testing.T) { 19 | ctrl := gomock.NewController(t) 20 | defer ctrl.Finish() 21 | 22 | mockStorage := mocks2.NewMockStorage(ctrl) 23 | mockGetAccountUC := mocks.NewMockGetZksAccountUseCase(ctrl) 24 | ctx := log.Context(context.Background(), log.Default()) 25 | address := "0xaddress" 26 | namespace := "namespace" 27 | 28 | mockGetAccountUC.EXPECT().WithStorage(mockStorage).Return(mockGetAccountUC).AnyTimes() 29 | 30 | usecase := NewSignUseCase(mockGetAccountUC).WithStorage(mockStorage) 31 | 32 | t.Run("should execute use case successfully", func(t *testing.T) { 33 | account := apputils.FakeZksAccount() 34 | 35 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(account, nil) 36 | 37 | signature, err := usecase.Execute(ctx, address, namespace, "0xdaaa") 38 | 39 | assert.NoError(t, err) 40 | assert.NotEmpty(t, signature) 41 | }) 42 | 43 | t.Run("should fail with same error if Get Account fails", func(t *testing.T) { 44 | expectedErr := fmt.Errorf("error") 45 | 46 | mockGetAccountUC.EXPECT().Execute(ctx, gomock.Any(), gomock.Any()).Return(nil, expectedErr) 47 | 48 | signature, err := usecase.Execute(ctx, address, namespace, "0xdaaa") 49 | 50 | assert.Empty(t, signature) 51 | assert.Equal(t, expectedErr, err) 52 | }) 53 | 54 | t.Run("should fail if creation of EDDSA private key fails", func(t *testing.T) { 55 | account := apputils.FakeZksAccount() 56 | account.PrivateKey = "account.PrivateKey" 57 | 58 | mockGetAccountUC.EXPECT().Execute(ctx, address, namespace).Return(account, nil) 59 | 60 | signature, err := usecase.Execute(ctx, address, namespace, "0xdaaa") 61 | 62 | assert.Empty(t, signature) 63 | assert.Error(t, err) 64 | }) 65 | 66 | t.Run("should fail with InvalidParameterError if data is not a hex string", func(t *testing.T) { 67 | key := apputils.FakeKey() 68 | key.Curve = entities.Secp256k1 69 | key.Algorithm = entities.ECDSA 70 | key.PrivateKey = "account.PrivateKey" 71 | 72 | signature, err := usecase.Execute(ctx, address, namespace, "invalid data") 73 | 74 | assert.Empty(t, signature) 75 | assert.True(t, errors.IsInvalidParameterError(err)) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /src/vault_test.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/vault/sdk/logical" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewVaultBackend(t *testing.T) { 12 | backend, err := NewVaultBackend(context.Background(), &logical.BackendConfig{}) 13 | 14 | assert.NoError(t, err) 15 | assert.NotEmpty(t, backend) 16 | } 17 | --------------------------------------------------------------------------------