diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 3000e537..1380bf42 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.19 + go-version: '1.20' - name: Build and Test run: make diff --git a/.golangci.yml b/.golangci.yml index f41e2a94..d0cfabbc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,7 +42,6 @@ linters: - structcheck enable: - bodyclose - - deadcode - dogsled - errcheck - goconst diff --git a/Dockerfile b/Dockerfile index 4b15882b..504fc332 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM golang:1.19-buster AS builder +FROM golang:1.20-bullseye AS builder ARG BUILD_VERSION ENV BUILD_VERSION=${BUILD_VERSION} ADD . /ffsigner WORKDIR /ffsigner RUN make -FROM debian:buster-slim +FROM debian:bullseye-slim WORKDIR /ffsigner RUN apt update -y \ && apt install -y curl jq \ diff --git a/Makefile b/Makefile index f0737fba..5a8abad1 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ lint: ${LINT} ${MOCKERY}: $(VGO) install github.com/vektra/mockery/cmd/mockery@latest ${LINT}: - $(VGO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.0 + $(VGO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.0 define makemock @@ -30,10 +30,11 @@ mocks-$(strip $(1))-$(strip $(2)): ${MOCKERY} ${MOCKERY} --case underscore --dir $(1) --name $(2) --outpkg $(3) --output mocks/$(strip $(3)) endef -$(eval $(call makemock, pkg/ethsigner, Wallet, ethsignermocks)) -$(eval $(call makemock, pkg/secp256k1, Signer, secp256k1mocks)) -$(eval $(call makemock, internal/rpcserver, Server, rpcservermocks)) -$(eval $(call makemock, pkg/rpcbackend, Backend, rpcbackendmocks)) +$(eval $(call makemock, pkg/ethsigner, Wallet, ethsignermocks)) +$(eval $(call makemock, pkg/secp256k1, Signer, secp256k1mocks)) +$(eval $(call makemock, pkg/secp256k1, SignerDirect, secp256k1mocks)) +$(eval $(call makemock, internal/rpcserver, Server, rpcservermocks)) +$(eval $(call makemock, pkg/rpcbackend, Backend, rpcbackendmocks)) firefly-signer: ${GOFILES} $(VGO) build -o ./firefly-signer -ldflags "-X main.buildDate=`date -u +\"%Y-%m-%dT%H:%M:%SZ\"` -X main.buildVersion=$(BUILD_VERSION)" -tags=prod -tags=prod -v ./ffsigner diff --git a/README.md b/README.md index b08d7efe..9350a9e7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,10 @@ A set of Ethereum transaction signing utilities designed for use across projects - Original - EIP-155 - EIP-1559 + - EIP-712 (see below) - See `pkg/ethsigner` [go doc](https://pkg.go.dev/github.com/hyperledger/firefly-signer/pkg/ethsigner) +- EIP-712 Typed Data implementation + - See `pkg/eip712` [go doc](https://pkg.go.dev/github.com/hyperledger/firefly-signer/pkg/eip712) - Keystore V3 key file implementation - Scrypt - read/write - pbkdf2 - read @@ -30,6 +33,10 @@ A set of Ethereum transaction signing utilities designed for use across projects - Files can be Keystore V3 files directly, with accompanying `{{ADDRESS}}.pass` files - Detects newly added files automatically - See `pkg/fswallet` [go doc](https://pkg.go.dev/github.com/hyperledger/firefly-signer/pkg/fswallet) +- JSON/RPC client + - HTTP + - WebSockets - with `eth_subscribe` support + - See `pkg/rpcbackend` [go doc](https://pkg.go.dev/github.com/hyperledger/firefly-signer/pkg/rpcbackend) ## JSON/RPC proxy server diff --git a/config.md b/config.md index 9d159626..aa0c42c7 100644 --- a/config.md +++ b/config.md @@ -64,6 +64,7 @@ nav_order: 2 |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` +|insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -76,6 +77,7 @@ nav_order: 2 |initialConnectAttempts|The number of attempts FireFly will make to connect to the WebSocket when starting up, before failing|`int`|`5` |path|The WebSocket sever URL to which FireFly should connect|WebSocket URL `string`|`` |readBufferSize|The size in bytes of the read buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`16Kb` +|url|URL to use for WebSocket - overrides url one level up (in the HTTP config)|`string`|`` |writeBufferSize|The size in bytes of the write buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`16Kb` ## cors @@ -183,5 +185,6 @@ nav_order: 2 |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` +|insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` \ No newline at end of file diff --git a/go.mod b/go.mod index 5d2a1e6c..388e96ee 100644 --- a/go.mod +++ b/go.mod @@ -1,75 +1,78 @@ module github.com/hyperledger/firefly-signer -go 1.19 +go 1.20 require ( - github.com/btcsuite/btcd/btcec/v2 v2.1.3 - github.com/fsnotify/fsnotify v1.6.0 + github.com/btcsuite/btcd/btcec/v2 v2.3.2 + github.com/fsnotify/fsnotify v1.7.0 github.com/go-resty/resty/v2 v2.7.0 github.com/gorilla/mux v1.8.0 - github.com/hyperledger/firefly-common v1.3.1-0.20231003142556-37246a8c5e72 + github.com/hyperledger/firefly-common v1.4.1-0.20231129162504-c603e8182052 github.com/karlseguin/ccache v2.0.3+incompatible github.com/pelletier/go-toml v1.9.5 - github.com/santhosh-tekuri/jsonschema/v5 v5.0.2 - github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.14.0 - github.com/stretchr/testify v1.8.1 - golang.org/x/crypto v0.1.0 - golang.org/x/text v0.8.0 + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.17.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.14.0 + golang.org/x/text v0.13.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/aidarkhanov/nanoid v1.0.8 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/getkin/kin-openapi v0.116.0 // indirect + github.com/getkin/kin-openapi v0.120.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect - github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.30.0 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/rs/cors v1.8.2 // indirect - github.com/spf13/afero v1.9.2 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rs/cors v1.10.1 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/stretchr/objx v0.5.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect gitlab.com/hfuss/mux-prometheus v0.0.5 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect; indirectå - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 81c5e9de..f35bcee6 100644 --- a/go.sum +++ b/go.sum @@ -36,27 +36,18 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/aidarkhanov/nanoid v1.0.8 h1:yxyJkgsEDFXP7+97vc6JevMcjyb03Zw+/9fqhlVXBXA= github.com/aidarkhanov/nanoid v1.0.8/go.mod h1:vadfZHT+m4uDhttg0yY4wW3GKtl2T6i4d2Age+45pYk= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= -github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 h1:MSskdM4/xJYcFzy0altH/C/xHopifpWzHUi1JeVI34Q= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -66,12 +57,12 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -80,34 +71,24 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/getkin/kin-openapi v0.116.0 h1:o986hwgMzR972JzOG5j6+WTwWqllZLs1EJKMKCivs2E= -github.com/getkin/kin-openapi v0.116.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= +github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -134,8 +115,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -149,7 +128,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -165,8 +143,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -178,147 +156,112 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hyperledger/firefly-common v1.3.1-0.20231003142556-37246a8c5e72 h1:l/Yo3woV2pih5EVmUHFwXr0LVr0twtXCVAdJAWInf5Q= -github.com/hyperledger/firefly-common v1.3.1-0.20231003142556-37246a8c5e72/go.mod h1:17lOH4YufiPy82LpKm8fPa/YXJ0pUyq01zK1CmklJwM= +github.com/hyperledger/firefly-common v1.4.1-0.20231129162504-c603e8182052 h1:HCOU/wsY3SvD6M5qHt4j4RgV8s/eFtvPiqo6VHDZNis= +github.com/hyperledger/firefly-common v1.4.1-0.20231129162504-c603e8182052/go.mod h1:79ZrPclGZwBOqyapN0qNR+EWZm5BSvXfmCfbBEABYbM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karlseguin/ccache v2.0.3+incompatible h1:j68C9tWOROiOLWTS/kCGg9IcJG+ACqn5+0+t8Oh83UU= github.com/karlseguin/ccache v2.0.3+incompatible/go.mod h1:CM9tNPzT6EdRh14+jiW8mEF9mkNZuuE51qmgGYUB93w= github.com/karlseguin/expect v1.0.8 h1:Bb0H6IgBWQpadY25UDNkYPDB9ITqK1xnSoZfAq362fw= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= -github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v5 v5.0.2 h1:zOYFITq/5SO7YOv39/Taw8s1skb0Py39K5V2XvCEP48= -github.com/santhosh-tekuri/jsonschema/v5 v5.0.2/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= @@ -335,16 +278,17 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -355,6 +299,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -380,7 +326,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -388,7 +333,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -411,10 +355,10 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -424,7 +368,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -436,12 +379,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -451,7 +391,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -464,8 +403,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -473,20 +410,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -494,8 +429,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -639,26 +575,19 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 5bff7174..5ec6fdcf 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -85,4 +85,16 @@ var ( MsgSubscribeResponseInvalid = ffe("FF22066", "Subscription response invalid") MsgWebSocketReconnected = ffe("FF22067", "WebSocket reconnected during JSON/RPC call") MsgContextCancelledWSConnect = ffe("FF22068", "Context canceled while connecting WebSocket") + MsgNotElementary = ffe("FF22069", "Not elementary type: %s") + MsgEIP712UnknownABICompType = ffe("FF22070", "Unknown ABI component type: %s") + MsgEIP712UnsupportedStrType = ffe("FF22071", "Unsupported type: %s") + MsgEIP712UnsupportedABIType = ffe("FF22072", "ABI type not supported by EIP-712 encoding: %s") + MsgEIP712TypeNotFound = ffe("FF22073", "Type '%s' not found in type map") + MsgEIP712PrimaryNotTuple = ffe("FF22074", "Type primary type must be a struct/tuple: %s") + MsgEIP712BadInternalType = ffe("FF22075", "Failed to extract struct name from ABI internalType '%s'") + MsgEIP712ValueNotMap = ffe("FF22076", "Value for struct '%s' not a map (%T)") + MsgEIP712InvalidArraySuffix = ffe("FF22077", "Type '%s' has invalid array suffix") + MsgEIP712ValueNotArray = ffe("FF22078", "Value for '%s' not an array (%T)") + MsgEIP712InvalidArrayLen = ffe("FF22079", "Value for '%s' must have %d entries (found %d)") + MsgEIP712PrimaryTypeRequired = ffe("FF22080", "Primary type must be specified") ) diff --git a/internal/signermsgs/en_field_descriptions.go b/internal/signermsgs/en_field_descriptions.go new file mode 100644 index 00000000..3fba68b1 --- /dev/null +++ b/internal/signermsgs/en_field_descriptions.go @@ -0,0 +1,55 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signermsgs + +var ( + ABIEntryAnonymous = ffm("EthABIEntry.anonymous", "If the event is anonymous then the signature of the event does not take up a topic slot") + ABIEntryType = ffm("EthABIEntry.type", "The type of the ABI entry: 'event', 'error', 'function', 'constructor', 'receive', or 'fallback'") + ABIEntryName = ffm("EthABIEntry.name", "The name of the ABI entry") + ABIEntryInputs = ffm("EthABIEntry.inputs", "Array of ABI parameter definitions") + ABIEntryOutputs = ffm("EthABIEntry.outputs", "Array of ABI parameter definitions") + ABIEntryStateMutability = ffm("EthABIEntry.stateMutability", "The state mutability of the function: 'pure', 'view', 'nonpayable' (the default) and 'payable'") + ABIEntryConstant = ffm("EthABIEntry.constant", "Functions only: Superseded by stateMutability payable/nonpayable") + ABIEntryPayable = ffm("EthABIEntry.payable", "Functions only: Superseded by stateMutability pure/view") + + ABIParameterName = ffm("EthABIParameter.name", "The name of the parameter") + ABIParameterType = ffm("EthABIParameter.type", "The type of the parameter per the ABI specification") + ABIParameterComponents = ffm("EthABIParameter.components", "An array of components, if the parameter is a tuple") + ABIParameterIndexed = ffm("EthABIParameter.indexed", "Whether this parameter uses one of the topics, or is in the data area") + ABIParameterInternalType = ffm("EthABIParameter.internalType", "Used by the solc compiler to include additional details - importantly the struct name for tuples") + + EthTransactionFrom = ffm("EthTransaction.internalType", "The from address (not encoded into the transaction directly, but used on this structure on input)") + EthTransactionNonce = ffm("EthTransaction.nonce", "Number used once (nonce) that specifies the sequence of this transaction in all transactions sent to the chain from this signing address") + EthTransactionGasPrice = ffm("EthTransaction.gasPrice", "The price per unit offered for the gas used when executing this transaction, if submitting to a chain that requires gas fees (in wei of the native chain token)") + EthTransactionMaxPriorityFeePerGas = ffm("EthTransaction.maxPriorityFeePerGas", "Part of the EIP-1559 extension to transaction pricing. The amount provided to the miner of the block per unit of gas, in addition to the base fee (which is burned when the block is mined)") + EthTransactionMaxFeePerGas = ffm("EthTransaction.maxFeePerGas", "Part of the EIP-1559 extension to transaction pricing. The total amount you are willing to pay per unit of gas used by your contract, which is the total of the baseFeePerGas (determined by the chain at execution time) and the maxPriorityFeePerGas") + EthTransactionGas = ffm("EthTransaction.gas", "The gas limit for execution of your transaction. Must be provided regardless of whether you paying a fee for the gas") + EthTransactionTo = ffm("EthTransaction.to", "The target address of the transaction. Omitted for contract deployments") + EthTransactionValue = ffm("EthTransaction.value", "An optional amount of native token to transfer along with the transaction (in wei)") + EthTransactionData = ffm("EthTransaction.data", "The encoded and signed transaction payload") + + EIP712ResultHash = ffm("EIP712Result.hash", "The EIP-712 hash generated according to the Typed Data V4 algorithm") + EIP712ResultSignatureRSV = ffm("EIP712Result.signatureRSV", "Hex encoded array of 65 bytes containing the R, S & V of the ECDSA signature. This is the standard signature encoding used in Ethereum recover utilities (note that some other utilities might expect a different encoding/packing of the data)") + EIP712ResultV = ffm("EIP712Result.v", "The V value of the ECDSA signature as a hex encoded integer") + EIP712ResultR = ffm("EIP712Result.r", "The R value of the ECDSA signature as a 32byte hex encoded array") + EIP712ResultS = ffm("EIP712Result.s", "The S value of the ECDSA signature as a 32byte hex encoded array") + + TypedDataDomain = ffm("TypedData.domain", "The data to encode into the EIP712Domain as part fo signing the transaction") + TypedDataMessage = ffm("TypedData.message", "The data to encode into primaryType structure, with nested values for any sub-structures") + TypedDataTypes = ffm("TypedData.types", "Array of types to use when encoding, which must include the primaryType and the EIP712Domain (noting the primary type can be EIP712Domain if the message is empty)") + TypedDataPrimaryType = ffm("TypedData.primaryType", "The primary type to begin encoding the EIP-712 hash from in the list of types, using the input message (unless set directly to EIP712Domain, in which case the message can be omitted)") +) diff --git a/mocks/ethsignermocks/wallet.go b/mocks/ethsignermocks/wallet.go index b6136e67..e879a103 100644 --- a/mocks/ethsignermocks/wallet.go +++ b/mocks/ethsignermocks/wallet.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.26.1. DO NOT EDIT. +// Code generated by mockery v2.37.1. DO NOT EDIT. package ethsignermocks @@ -110,13 +110,12 @@ func (_m *Wallet) Sign(ctx context.Context, txn *ethsigner.Transaction, chainID return r0, r1 } -type mockConstructorTestingTNewWallet interface { +// NewWallet creates a new instance of Wallet. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewWallet(t interface { mock.TestingT Cleanup(func()) -} - -// NewWallet creates a new instance of Wallet. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewWallet(t mockConstructorTestingTNewWallet) *Wallet { +}) *Wallet { mock := &Wallet{} mock.Mock.Test(t) diff --git a/mocks/rpcbackendmocks/backend.go b/mocks/rpcbackendmocks/backend.go index e5865fa2..c98bd97d 100644 --- a/mocks/rpcbackendmocks/backend.go +++ b/mocks/rpcbackendmocks/backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.26.1. DO NOT EDIT. +// Code generated by mockery v2.37.1. DO NOT EDIT. package rpcbackendmocks @@ -59,13 +59,12 @@ func (_m *Backend) SyncRequest(ctx context.Context, rpcReq *rpcbackend.RPCReques return r0, r1 } -type mockConstructorTestingTNewBackend interface { +// NewBackend creates a new instance of Backend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBackend(t interface { mock.TestingT Cleanup(func()) -} - -// NewBackend creates a new instance of Backend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBackend(t mockConstructorTestingTNewBackend) *Backend { +}) *Backend { mock := &Backend{} mock.Mock.Test(t) diff --git a/mocks/rpcservermocks/server.go b/mocks/rpcservermocks/server.go index 7ff7a0e5..b28604ff 100644 --- a/mocks/rpcservermocks/server.go +++ b/mocks/rpcservermocks/server.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.26.1. DO NOT EDIT. +// Code generated by mockery v2.37.1. DO NOT EDIT. package rpcservermocks @@ -42,13 +42,12 @@ func (_m *Server) WaitStop() error { return r0 } -type mockConstructorTestingTNewServer interface { +// NewServer creates a new instance of Server. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewServer(t interface { mock.TestingT Cleanup(func()) -} - -// NewServer creates a new instance of Server. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewServer(t mockConstructorTestingTNewServer) *Server { +}) *Server { mock := &Server{} mock.Mock.Test(t) diff --git a/mocks/secp256k1mocks/signer.go b/mocks/secp256k1mocks/signer.go index bab05349..435ad220 100644 --- a/mocks/secp256k1mocks/signer.go +++ b/mocks/secp256k1mocks/signer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.26.1. DO NOT EDIT. +// Code generated by mockery v2.37.1. DO NOT EDIT. package secp256k1mocks @@ -12,17 +12,17 @@ type Signer struct { mock.Mock } -// Sign provides a mock function with given fields: message -func (_m *Signer) Sign(message []byte) (*secp256k1.SignatureData, error) { - ret := _m.Called(message) +// Sign provides a mock function with given fields: msgToHashAndSign +func (_m *Signer) Sign(msgToHashAndSign []byte) (*secp256k1.SignatureData, error) { + ret := _m.Called(msgToHashAndSign) var r0 *secp256k1.SignatureData var r1 error if rf, ok := ret.Get(0).(func([]byte) (*secp256k1.SignatureData, error)); ok { - return rf(message) + return rf(msgToHashAndSign) } if rf, ok := ret.Get(0).(func([]byte) *secp256k1.SignatureData); ok { - r0 = rf(message) + r0 = rf(msgToHashAndSign) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*secp256k1.SignatureData) @@ -30,7 +30,7 @@ func (_m *Signer) Sign(message []byte) (*secp256k1.SignatureData, error) { } if rf, ok := ret.Get(1).(func([]byte) error); ok { - r1 = rf(message) + r1 = rf(msgToHashAndSign) } else { r1 = ret.Error(1) } @@ -38,13 +38,12 @@ func (_m *Signer) Sign(message []byte) (*secp256k1.SignatureData, error) { return r0, r1 } -type mockConstructorTestingTNewSigner interface { +// NewSigner creates a new instance of Signer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSigner(t interface { mock.TestingT Cleanup(func()) -} - -// NewSigner creates a new instance of Signer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSigner(t mockConstructorTestingTNewSigner) *Signer { +}) *Signer { mock := &Signer{} mock.Mock.Test(t) diff --git a/mocks/secp256k1mocks/signer_direct.go b/mocks/secp256k1mocks/signer_direct.go new file mode 100644 index 00000000..6bf5f346 --- /dev/null +++ b/mocks/secp256k1mocks/signer_direct.go @@ -0,0 +1,79 @@ +// Code generated by mockery v2.37.1. DO NOT EDIT. + +package secp256k1mocks + +import ( + secp256k1 "github.com/hyperledger/firefly-signer/pkg/secp256k1" + mock "github.com/stretchr/testify/mock" +) + +// SignerDirect is an autogenerated mock type for the SignerDirect type +type SignerDirect struct { + mock.Mock +} + +// Sign provides a mock function with given fields: msgToHashAndSign +func (_m *SignerDirect) Sign(msgToHashAndSign []byte) (*secp256k1.SignatureData, error) { + ret := _m.Called(msgToHashAndSign) + + var r0 *secp256k1.SignatureData + var r1 error + if rf, ok := ret.Get(0).(func([]byte) (*secp256k1.SignatureData, error)); ok { + return rf(msgToHashAndSign) + } + if rf, ok := ret.Get(0).(func([]byte) *secp256k1.SignatureData); ok { + r0 = rf(msgToHashAndSign) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*secp256k1.SignatureData) + } + } + + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(msgToHashAndSign) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SignDirect provides a mock function with given fields: message +func (_m *SignerDirect) SignDirect(message []byte) (*secp256k1.SignatureData, error) { + ret := _m.Called(message) + + var r0 *secp256k1.SignatureData + var r1 error + if rf, ok := ret.Get(0).(func([]byte) (*secp256k1.SignatureData, error)); ok { + return rf(message) + } + if rf, ok := ret.Get(0).(func([]byte) *secp256k1.SignatureData); ok { + r0 = rf(message) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*secp256k1.SignatureData) + } + } + + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(message) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewSignerDirect creates a new instance of SignerDirect. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSignerDirect(t interface { + mock.TestingT + Cleanup(func()) +}) *SignerDirect { + mock := &SignerDirect{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index b6ab01df..4a1e88e7 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -227,23 +227,23 @@ type ParameterArray []*Parameter // Defines the name / inputs / outputs which can be used to generate the signature // of the function/event, and used to encode input data, or decode output data. type Entry struct { - Type EntryType `json:"type,omitempty"` // Type of the entry - there are multiple function sub-types, events and errors - Name string `json:"name,omitempty"` // Name of the function/event/error - Payable bool `json:"payable,omitempty"` // Functions only: Superseded by stateMutability payable/nonpayable - Constant bool `json:"constant,omitempty"` // Functions only: Superseded by stateMutability pure/view - Anonymous bool `json:"anonymous,omitempty"` // Events only: The event is emitted without a signature (topic[0] is not generated) - StateMutability StateMutability `json:"stateMutability,omitempty"` // How the function interacts with the blockchain state - Inputs ParameterArray `json:"inputs"` // The list of input parameters to a function, or fields of an event / error - Outputs ParameterArray `json:"outputs"` // Functions only: The list of return values from a function + Type EntryType `ffstruct:"EthABIEntry" json:"type,omitempty"` // Type of the entry - there are multiple function sub-types, events and errors + Name string `ffstruct:"EthABIEntry" json:"name,omitempty"` // Name of the function/event/error + Payable bool `ffstruct:"EthABIEntry" json:"payable,omitempty"` // Functions only: Superseded by stateMutability payable/nonpayable + Constant bool `ffstruct:"EthABIEntry" json:"constant,omitempty"` // Functions only: Superseded by stateMutability pure/view + Anonymous bool `ffstruct:"EthABIEntry" json:"anonymous,omitempty"` // Events only: The event is emitted without a signature (topic[0] is not generated) + StateMutability StateMutability `ffstruct:"EthABIEntry" json:"stateMutability,omitempty"` // How the function interacts with the blockchain state + Inputs ParameterArray `ffstruct:"EthABIEntry" json:"inputs"` // The list of input parameters to a function, or fields of an event / error + Outputs ParameterArray `ffstruct:"EthABIEntry" json:"outputs"` // Functions only: The list of return values from a function } // Parameter is an individual typed parameter input/output type Parameter struct { - Name string `json:"name"` // The name of the argument - does not affect the signature - Type string `json:"type"` // The canonical type of the parameter - InternalType string `json:"internalType,omitempty"` // Additional internal type information that might be generated by the compiler - Components ParameterArray `json:"components,omitempty"` // An ordered list (tuple) of nested elements for array/object types - Indexed bool `json:"indexed,omitempty"` // Events only: Whether the parameter is indexed into one of the topics of the log, or in the log's data segment + Name string `ffstruct:"EthABIParameter" json:"name"` // The name of the argument - does not affect the signature + Type string `ffstruct:"EthABIParameter" json:"type"` // The canonical type of the parameter + InternalType string `ffstruct:"EthABIParameter" json:"internalType,omitempty"` // Additional internal type information that might be generated by the compiler + Components ParameterArray `ffstruct:"EthABIParameter" json:"components,omitempty"` // An ordered list (tuple) of nested elements for array/object types + Indexed bool `ffstruct:"EthABIParameter" json:"indexed,omitempty"` // Events only: Whether the parameter is indexed into one of the topics of the log, or in the log's data segment parsed *typeComponent // cached components } diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index d03506ac..1181f63c 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -23,6 +23,7 @@ import ( "math/big" "testing" + "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/stretchr/testify/assert" ) @@ -296,11 +297,16 @@ func TestABIGetTupleTypeTree(t *testing.T) { assert.Equal(t, TupleComponent, tc.ComponentType()) assert.Len(t, tc.TupleChildren(), 3) assert.Equal(t, "(uint256,string[2],bytes)", tc.String()) + assert.False(t, tc.ElementaryFixed()) // not fixed, as not elementary assert.Equal(t, ElementaryComponent, tc.TupleChildren()[0].ComponentType()) assert.Equal(t, ElementaryTypeUint, tc.TupleChildren()[0].ElementaryType()) + assert.Equal(t, "256", tc.TupleChildren()[0].ElementarySuffix()) // alias resolved + assert.True(t, tc.TupleChildren()[0].ElementaryFixed()) assert.Equal(t, FixedArrayComponent, tc.TupleChildren()[1].ComponentType()) + assert.Equal(t, 2, tc.TupleChildren()[1].FixedArrayLen()) + assert.Equal(t, BaseTypeString, tc.TupleChildren()[1].ArrayChild().ElementaryType().BaseType()) assert.Equal(t, ElementaryComponent, tc.TupleChildren()[1].ArrayChild().ComponentType()) assert.Equal(t, ElementaryTypeString, tc.TupleChildren()[1].ArrayChild().ElementaryType()) @@ -837,3 +843,7 @@ func TestEncodeCallDataValuesHelper(t *testing.T) { assert.Equal(t, "113bc475000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", hex.EncodeToString(b)) } + +func TestABIDocumented(t *testing.T) { + ffapi.CheckObjectDocumented(&ABI{}) +} diff --git a/pkg/abi/inputparsing.go b/pkg/abi/inputparsing.go index 4b3c2a26..6c62933e 100644 --- a/pkg/abi/inputparsing.go +++ b/pkg/abi/inputparsing.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -49,6 +49,19 @@ func (cv *ComponentValue) JSON() ([]byte, error) { return NewSerializer().SerializeJSON(cv) } +func (cv *ComponentValue) ElementaryABIData() ([]byte, bool, error) { + return cv.ElementaryABIDataCtx(context.Background()) +} + +func (cv *ComponentValue) ElementaryABIDataCtx(ctx context.Context) (data []byte, dynamic bool, err error) { + c := cv.Component + et := cv.Component.ElementaryType().(*elementaryTypeInfo) + if et == nil { + return nil, false, i18n.NewError(ctx, signermsgs.MsgNotElementary, c.String()) + } + return et.encodeABIData(ctx, c.String(), c.(*typeComponent), cv.Value) +} + // getPtrValOrRawTypeNil sees if v is a pointer, with a non-nil value. If so returns that value, else nil func getPtrValOrNil(v interface{}) interface{} { val := reflect.ValueOf(v) @@ -306,7 +319,7 @@ func getBytesFromInterface(ctx context.Context, desc string, v interface{}) ([]b vt = strings.TrimPrefix(vt, "0x") hb, err := hex.DecodeString(vt) if err != nil { - return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidHexABIInput, vt, v, desc) + return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidHexABIInput, v, desc) } return hb, nil default: @@ -320,7 +333,7 @@ func getBytesFromInterface(ctx context.Context, desc string, v interface{}) ([]b if vi != nil { return getBytesFromInterface(ctx, desc, vi) } - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidHexABIInput, vt, v) + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidHexABIInput, v, desc) } } @@ -366,17 +379,21 @@ func getStringInterfaceMap(ctx context.Context, breadcrumbs string, input interf return iMap, nil } +func (tc *typeComponent) readElementaryType(ctx context.Context, breadcrumbs string, input interface{}) (cv *ComponentValue, err error) { + value, err := tc.elementaryType.readExternalData(ctx, breadcrumbs, input) + if err != nil { + return nil, err + } + return &ComponentValue{ + Component: tc, + Value: value, + }, nil +} + func walkInput(ctx context.Context, breadcrumbs string, input interface{}, component *typeComponent) (cv *ComponentValue, err error) { switch component.cType { case ElementaryComponent: - value, err := component.elementaryType.readExternalData(ctx, breadcrumbs, input) - if err != nil { - return nil, err - } - return &ComponentValue{ - Component: component, - Value: value, - }, nil + return component.readElementaryType(ctx, breadcrumbs, input) case FixedArrayComponent, DynamicArrayComponent: return walkArrayInput(ctx, breadcrumbs, input, component) case TupleComponent: diff --git a/pkg/abi/inputparsing_test.go b/pkg/abi/inputparsing_test.go index facb4c96..0bfb9ad0 100644 --- a/pkg/abi/inputparsing_test.go +++ b/pkg/abi/inputparsing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/stretchr/testify/assert" ) @@ -673,3 +674,38 @@ func TestTuplesMissingName(t *testing.T) { _, err = inputs.ParseJSON([]byte(values)) assert.Regexp(t, "FF22039", err) } + +func TestTupleEncodeIndividualFixedParam(t *testing.T) { + const sample = `[ + { + "name": "foo", + "type": "function", + "inputs": [ + { + "name": "a", + "type": "int" + } + ], + "outputs": [] + } + ]` + + inputs := testABI(t, sample)[0].Inputs + + // Fine if you use the array syntax + values := `{ "a": 12345 }` + cv, err := inputs.ParseJSON([]byte(values)) + assert.NoError(t, err) + assert.Equal(t, TupleComponent, cv.Component.ComponentType()) + assert.Len(t, cv.Children, 1) + _, _, err = cv.ElementaryABIData() + assert.Regexp(t, "FF22069", err) + + intComp := cv.Children[0] + assert.Equal(t, ElementaryComponent, intComp.Component.ComponentType()) + b, dynamic, err := intComp.ElementaryABIData() + assert.NoError(t, err) + assert.False(t, dynamic) + assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000003039", ethtypes.HexBytes0xPrefix(b).String()) + +} diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index d317e15a..79c4dc42 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -47,12 +47,16 @@ type TypeComponent interface { String() string // gives the signature for this type level of the type component hierarchy ComponentType() ComponentType // classification of the component type (tuple, array or elemental) ElementaryType() ElementaryTypeInfo // only non-nil for elementary components + ElementarySuffix() string // only on elementary types with a suffix - expands "aliases" (so "uint" would have "256") + ElementaryFixed() bool // whether the elementary type if fixed ArrayChild() TypeComponent // only non-nil for array components + FixedArrayLen() int // only for fixed-array components TupleChildren() []TypeComponent // only non-nil for tuple components KeyName() string // the name of the ABI property/component, only set for top-level parameters and tuple entries Parameter() *Parameter // the ABI property/component, only set for top-level parameters and tuple entries ParseExternal(v interface{}) (*ComponentValue, error) ParseExternalCtx(ctx context.Context, v interface{}) (*ComponentValue, error) + ParseExternalDesc(ctx context.Context, v interface{}, desc string) (*ComponentValue, error) DecodeABIData(d []byte, offset int) (*ComponentValue, error) DecodeABIDataCtx(ctx context.Context, d []byte, offest int) (*ComponentValue, error) } @@ -73,7 +77,7 @@ type typeComponent struct { // elementaryTypeInfo defines the string parsing rules, as well as a pointer to the functions for // serialization to a set of bytes, and back again type elementaryTypeInfo struct { - name string // The name of the type - the alphabetic characters up to an optional suffix + name BaseTypeName // The name of the type - the alphabetic characters up to an optional suffix suffixType suffixType // Whether there is a length suffix, and its type defaultSuffix string // If set and there is no suffix supplied, the following suffix is used defaultM uint16 // If the type implicitly has an M value that is not expressed (such as "function") @@ -85,11 +89,13 @@ type elementaryTypeInfo struct { fixed32 bool // True if the is at most 32 bytes in length, so directly fits into an event topic dynamic func(c *typeComponent) bool // True if the type is dynamic length jsonEncodingType JSONEncodingType // categorizes how the type can be read/written from input JSON data - readExternalData func(ctx context.Context, desc string, input interface{}) (interface{}, error) + readExternalData DataReader encodeABIData func(ctx context.Context, desc string, tc *typeComponent, value interface{}) (data []byte, dynamic bool, err error) decodeABIData func(ctx context.Context, desc string, block []byte, headStart, headPosition int, component *typeComponent) (cv *ComponentValue, err error) } +type DataReader func(ctx context.Context, desc string, input interface{}) (interface{}, error) + func (et *elementaryTypeInfo) String() string { switch et.suffixType { case suffixTypeMOptional, suffixTypeMRequired: @@ -114,7 +120,7 @@ func (et *elementaryTypeInfo) String() string { } return s default: - return et.name + return string(et.name) } } @@ -122,7 +128,15 @@ func (et *elementaryTypeInfo) JSONEncodingType() JSONEncodingType { return et.jsonEncodingType } -var elementaryTypes = map[string]*elementaryTypeInfo{} +func (et *elementaryTypeInfo) BaseType() BaseTypeName { + return et.name +} + +func (et *elementaryTypeInfo) DataReader() DataReader { + return et.readExternalData +} + +var elementaryTypes = map[BaseTypeName]*elementaryTypeInfo{} func registerElementaryType(et elementaryTypeInfo) ElementaryTypeInfo { elementaryTypes[et.name] = &et @@ -133,8 +147,10 @@ func registerElementaryType(et elementaryTypeInfo) ElementaryTypeInfo { // You can do an equality check against the appropriate constant, to check if this is the type you are expecting. // e.g. type ElementaryTypeInfo interface { + BaseType() BaseTypeName // const for each of the elementary types String() string // gives a summary of the rules the elemental type (used in error reporting) JSONEncodingType() JSONEncodingType // categorizes JSON input/output type to one of a small number of options + DataReader() DataReader // allows a side-door into the ABI code read data - caller has to know what to expect } type JSONEncodingType int @@ -147,6 +163,20 @@ const ( JSONEncodingTypeString // JSON string containing any unicode characters ) +type BaseTypeName string + +const ( + BaseTypeInt BaseTypeName = "int" + BaseTypeUInt BaseTypeName = "uint" + BaseTypeAddress BaseTypeName = "address" + BaseTypeBool BaseTypeName = "bool" + BaseTypeFixed BaseTypeName = "fixed" + BaseTypeUFixed BaseTypeName = "ufixed" + BaseTypeBytes BaseTypeName = "bytes" + BaseTypeFunction BaseTypeName = "function" + BaseTypeString BaseTypeName = "string" +) + // tupleTypeString appears in the same place in the ABI as elementary type strings, but it is not an elementary type. // We treat it separately. const tupleTypeString = "tuple" @@ -155,7 +185,7 @@ var alwaysFixed = func(tc *typeComponent) bool { return false } var ( ElementaryTypeInt = registerElementaryType(elementaryTypeInfo{ - name: "int", + name: BaseTypeInt, suffixType: suffixTypeMRequired, defaultSuffix: "256", mMin: 8, @@ -171,7 +201,7 @@ var ( decodeABIData: decodeABISignedInt, }) ElementaryTypeUint = registerElementaryType(elementaryTypeInfo{ - name: "uint", + name: BaseTypeUInt, suffixType: suffixTypeMRequired, defaultSuffix: "256", mMin: 8, @@ -187,7 +217,7 @@ var ( decodeABIData: decodeABIUnsignedInt, }) ElementaryTypeAddress = registerElementaryType(elementaryTypeInfo{ - name: "address", + name: BaseTypeAddress, suffixType: suffixTypeNone, defaultM: 160, // encoded as "uint160" fixed32: true, @@ -200,7 +230,7 @@ var ( decodeABIData: decodeABIUnsignedInt, }) ElementaryTypeBool = registerElementaryType(elementaryTypeInfo{ - name: "bool", + name: BaseTypeBool, suffixType: suffixTypeNone, defaultM: 8, // encoded as "uint8" fixed32: true, @@ -213,7 +243,7 @@ var ( decodeABIData: decodeABIUnsignedInt, }) ElementaryTypeFixed = registerElementaryType(elementaryTypeInfo{ - name: "fixed", + name: BaseTypeFixed, suffixType: suffixTypeMxNRequired, defaultSuffix: "128x18", mMin: 8, @@ -231,7 +261,7 @@ var ( decodeABIData: decodeABISignedFloat, }) ElementaryTypeUfixed = registerElementaryType(elementaryTypeInfo{ - name: "ufixed", + name: BaseTypeUFixed, suffixType: suffixTypeMxNRequired, defaultSuffix: "128x18", mMin: 8, @@ -249,7 +279,7 @@ var ( decodeABIData: decodeABIUnsignedFloat, }) ElementaryTypeBytes = registerElementaryType(elementaryTypeInfo{ - name: "bytes", + name: BaseTypeBytes, suffixType: suffixTypeMOptional, // note that "bytes" without a suffix is a special dynamic sized byte sequence mMin: 1, mMax: 32, @@ -263,7 +293,7 @@ var ( decodeABIData: decodeABIBytes, }) ElementaryTypeFunction = registerElementaryType(elementaryTypeInfo{ - name: "function", + name: BaseTypeFunction, suffixType: suffixTypeNone, defaultM: 24, // encoded as "bytes24" fixed32: true, @@ -276,7 +306,7 @@ var ( decodeABIData: decodeABIBytes, }) ElementaryTypeString = registerElementaryType(elementaryTypeInfo{ - name: "string", + name: BaseTypeString, suffixType: suffixTypeNone, fixed32: false, dynamic: func(tc *typeComponent) bool { return true }, @@ -339,6 +369,17 @@ func (tc *typeComponent) ElementaryType() ElementaryTypeInfo { return tc.elementaryType } +func (tc *typeComponent) ElementarySuffix() string { + return tc.elementarySuffix +} + +func (tc *typeComponent) ElementaryFixed() bool { + if tc.elementaryType != nil { + return !tc.elementaryType.dynamic(tc) + } + return false +} + func (tc *typeComponent) KeyName() string { return tc.keyName } @@ -347,6 +388,10 @@ func (tc *typeComponent) ArrayChild() TypeComponent { return tc.arrayChild } +func (tc *typeComponent) FixedArrayLen() int { + return tc.arrayLength +} + func (tc *typeComponent) TupleChildren() []TypeComponent { children := make([]TypeComponent, len(tc.tupleChildren)) for i, c := range tc.tupleChildren { @@ -360,7 +405,11 @@ func (tc *typeComponent) ParseExternal(input interface{}) (*ComponentValue, erro } func (tc *typeComponent) ParseExternalCtx(ctx context.Context, input interface{}) (*ComponentValue, error) { - return tc.parseExternal(ctx, "", input) + return tc.ParseExternalDesc(ctx, input, "") +} + +func (tc *typeComponent) ParseExternalDesc(ctx context.Context, input interface{}, desc string) (*ComponentValue, error) { + return tc.parseExternal(ctx, desc, input) } func (tc *typeComponent) DecodeABIData(b []byte, offset int) (*ComponentValue, error) { @@ -414,7 +463,7 @@ func (p *Parameter) parseABIParameterComponents(ctx context.Context) (tc *typeCo } } } else { - et, ok := elementaryTypes[etStr] + et, ok := elementaryTypes[BaseTypeName(etStr)] if !ok { return nil, i18n.NewError(ctx, signermsgs.MsgUnsupportedABIType, etStr, abiTypeString) } diff --git a/pkg/abi/typecomponents_test.go b/pkg/abi/typecomponents_test.go index 17f5956a..64986708 100644 --- a/pkg/abi/typecomponents_test.go +++ b/pkg/abi/typecomponents_test.go @@ -35,6 +35,7 @@ func TestElementalTypeInfoRules(t *testing.T) { assert.Equal(t, "function", ElementaryTypeFunction.String()) assert.Equal(t, "string", ElementaryTypeString.String()) assert.Equal(t, JSONEncodingTypeString, ElementaryTypeString.JSONEncodingType()) + assert.NotNil(t, ElementaryTypeString.DataReader()) } diff --git a/pkg/eip712/abi_to_typed_data.go b/pkg/eip712/abi_to_typed_data.go new file mode 100644 index 00000000..796daede --- /dev/null +++ b/pkg/eip712/abi_to_typed_data.go @@ -0,0 +1,146 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package eip712 + +import ( + "context" + "fmt" + "regexp" + + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-signer/internal/signermsgs" + "github.com/hyperledger/firefly-signer/pkg/abi" +) + +var internalTypeStructExtractor = regexp.MustCompile(`^struct (.*\.)?([^.\[\]]+)(\[\d*\])*$`) + +// Convert an ABI tuple definition, into the EIP-712 structure that's embedded into the +// "eth_signTypedData" signing request payload. It's a much simpler structure that +// flattens out a map of types (requiring each type to be named by a struct definition) +func ABItoTypedDataV4(ctx context.Context, tc abi.TypeComponent) (primaryType string, typeSet TypeSet, err error) { + if tc.ComponentType() != abi.TupleComponent { + return "", nil, i18n.NewError(ctx, signermsgs.MsgEIP712PrimaryNotTuple, tc.String()) + } + primaryType, err = extractSolidityTypeName(ctx, tc.Parameter()) + if err != nil { + return "", nil, err + } + // First we need to build the sorted array of types for `encodeType` + typeSet = make(TypeSet) + if err := addABITypes(ctx, tc, typeSet); err != nil { + return "", nil, err + } + return primaryType, typeSet, nil +} + +// Maps a parsed ABI component type to an EIP-712 type string +// - Subset of elementary types, with aliases resolved +// - Struct types are simply the name of the type +// - Fixed and dynamic array suffixes are supported +func mapABIType(ctx context.Context, tc abi.TypeComponent) (string, error) { + switch tc.ComponentType() { + case abi.TupleComponent: + return extractSolidityTypeName(ctx, tc.Parameter()) + case abi.DynamicArrayComponent, abi.FixedArrayComponent: + child, err := mapABIType(ctx, tc.ArrayChild()) + if err != nil { + return "", err + } + if tc.ComponentType() == abi.FixedArrayComponent { + return fmt.Sprintf("%s[%d]", child, tc.FixedArrayLen()), nil + } + return child + "[]", nil + default: + return mapElementaryABIType(ctx, tc) + } +} + +// Maps one of the parsed ABI elementary types to an EIP-712 elementary type +func mapElementaryABIType(ctx context.Context, tc abi.TypeComponent) (string, error) { + if tc.ComponentType() != abi.ElementaryComponent { + return "", i18n.NewError(ctx, signermsgs.MsgNotElementary, tc) + } + et := tc.ElementaryType() + switch et.BaseType() { + case abi.BaseTypeAddress, abi.BaseTypeBool, abi.BaseTypeString, abi.BaseTypeInt, abi.BaseTypeUInt: + // Types that need no transposition + return string(et.BaseType()) + tc.ElementarySuffix(), nil + case abi.BaseTypeBytes: + // Bytes is special + if tc.ElementaryFixed() { + return string(et.BaseType()) + tc.ElementarySuffix(), nil + } + return string(et.BaseType()), nil + default: + // EIP-712 does not support the other types + return "", i18n.NewError(ctx, signermsgs.MsgEIP712UnsupportedABIType, tc) + } +} + +// ABI does not formally contain the Struct name - as it's not required for encoding the value. +// EIP-712 requires the Struct name as it is used through the standard, including to de-dup definitions +// +// Solidity uses the "internalType" field by convention as an extension to ABI, so we require +// that here for EIP-712 encoding to be successful. +func extractSolidityTypeName(ctx context.Context, param *abi.Parameter) (string, error) { + match := internalTypeStructExtractor.FindStringSubmatch(param.InternalType) + if match == nil { + return "", i18n.NewError(ctx, signermsgs.MsgEIP712BadInternalType, param.InternalType) + } + return match[2], nil +} + +// Recursively find all types, with a name -> encoded name map. +func addABITypes(ctx context.Context, tc abi.TypeComponent, typeSet TypeSet) error { + switch tc.ComponentType() { + case abi.TupleComponent: + typeName, err := extractSolidityTypeName(ctx, tc.Parameter()) + if err != nil { + return err + } + + if _, mapped := typeSet[typeName]; mapped { + // we've already mapped this type + return nil + } + t := make(Type, len(tc.TupleChildren())) + for i, child := range tc.TupleChildren() { + ts, err := mapABIType(ctx, child) + if err != nil { + return err + } + t[i] = &TypeMember{ + Name: child.KeyName(), + Type: ts, + } + } + typeSet[typeName] = t + // recurse + for _, child := range tc.TupleChildren() { + if err := addABITypes(ctx, child, typeSet); err != nil { + return err + } + } + return nil + case abi.DynamicArrayComponent, abi.FixedArrayComponent: + // recurse into the child + return addABITypes(ctx, tc.ArrayChild(), typeSet) + default: + // from a type collection perspective, this is a leaf - nothing to do + return nil + } +} diff --git a/pkg/eip712/abi_to_typed_data_test.go b/pkg/eip712/abi_to_typed_data_test.go new file mode 100644 index 00000000..9b751da0 --- /dev/null +++ b/pkg/eip712/abi_to_typed_data_test.go @@ -0,0 +1,420 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package eip712 + +import ( + "context" + "encoding/json" + "testing" + + "github.com/hyperledger/firefly-signer/pkg/abi" + "github.com/stretchr/testify/assert" +) + +func TestABISimpleStruct(t *testing.T) { + + // // Extracted from ABI in this solidity: + // // solc --combined-json abi eip712_examples.sol > eip712_examples.json + // pragma solidity ^0.8.0; + // contract EIP712Examples { + // struct Person { + // string name; + // address wallet; + // } + // struct Mail { + // Person from; + // Person to; + // string contents; + // } + // constructor() {} + // function mail() public pure returns (Mail memory) { + // return Mail(Person("", address(0)), Person("", address(0)), ""); + // } + // } + mailABI := []byte(`{ + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "wallet", + "type": "address" + } + ], + "internalType": "struct EIP712Examples.Person", + "name": "from", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "wallet", + "type": "address" + } + ], + "internalType": "struct EIP712Examples.Person", + "name": "to", + "type": "tuple" + }, + { + "internalType": "string", + "name": "contents", + "type": "string" + } + ], + "internalType": "struct EIP712Examples.Mail", + "name": "", + "type": "tuple" + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + pt, ts, err := ABItoTypedDataV4(context.Background(), tc) + assert.NoError(t, err) + assert.Equal(t, "Mail", pt) + assert.Equal(t, TypeSet{ + "Person": Type{ + { + Name: "name", + Type: "string", + }, + { + Name: "wallet", + Type: "address", + }, + }, + "Mail": Type{ + { + Name: "from", + Type: "Person", + }, + { + Name: "to", + Type: "Person", + }, + { + Name: "contents", + Type: "string", + }, + }, + }, ts) + +} + +func TestABIArraysAndTypes(t *testing.T) { + + // // solc --combined-json abi eip712_examples.sol > eip712_examples.json + // pragma solidity ^0.8.0; + // contract EIP712Examples { + // struct TopLevel { + // string[] strings; + // int32[5] ints; + // bool[][] multidim; + // Nested[] nested; + // } + // struct Nested { + // uint[] unaliased; + // bytes[5] bytestrings; + // bytes1[8] eightbytes; + // } + // constructor() {} + // function mail(TopLevel memory) public pure {} + // } + mailABI := []byte(`{ + "components": [ + { + "internalType": "string[]", + "name": "strings", + "type": "string[]" + }, + { + "internalType": "int32[5]", + "name": "ints", + "type": "int32[5]" + }, + { + "internalType": "bool[][]", + "name": "multidim", + "type": "bool[][]" + }, + { + "components": [ + { + "internalType": "uint256[]", + "name": "unaliased", + "type": "uint256[]" + }, + { + "internalType": "bytes[5]", + "name": "bytestrings", + "type": "bytes[5]" + }, + { + "internalType": "bytes1[8]", + "name": "eightbytes", + "type": "bytes1[8]" + } + ], + "internalType": "struct EIP712Examples.Nested[]", + "name": "nested", + "type": "tuple[]" + } + ], + "internalType": "struct EIP712Examples.TopLevel", + "name": "", + "type": "tuple" + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + pt, ts, err := ABItoTypedDataV4(context.Background(), tc) + assert.NoError(t, err) + assert.Equal(t, "TopLevel", pt) + assert.Equal(t, TypeSet{ + "Nested": Type{ + { + Name: "unaliased", + Type: "uint256[]", + }, + { + Name: "bytestrings", + Type: "bytes[5]", + }, + { + Name: "eightbytes", + Type: "bytes1[8]", + }, + }, + "TopLevel": Type{ + { + Name: "strings", + Type: "string[]", + }, + { + Name: "ints", + Type: "int32[5]", + }, + { + Name: "multidim", + Type: "bool[][]", + }, + { + Name: "nested", + Type: "Nested[]", + }, + }, + }, ts) + +} + +func TestABINotTuple(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "uint256" + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, _, err = ABItoTypedDataV4(context.Background(), tc) + assert.Regexp(t, "FF22074", err) + +} + +func TestABINoInternalType(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple", + "components": [] + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, _, err = ABItoTypedDataV4(context.Background(), tc) + assert.Regexp(t, "FF22075", err) + +} + +func TestABINoInternalTypeChild(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple", + "internalType": "struct MyType", + "components": [ + { + "name": "noInternal", + "type": "tuple", + "components": [] + } + ] + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, _, err = ABItoTypedDataV4(context.Background(), tc) + assert.Regexp(t, "FF22075", err) + +} + +func TestMapElementaryABITypeNonElementary(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple", + "internalType": "struct MyType", + "components": [] + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, err = mapElementaryABIType(context.Background(), tc) + assert.Regexp(t, "FF22069", err) + +} + +func TestMapABITypeBadArray(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple[]", + "components": [] + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, err = mapABIType(context.Background(), tc) + assert.Regexp(t, "FF22075", err) + +} + +func TestAddABITypesFailToExtractStructName(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple" + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + err = addABITypes(context.Background(), tc, TypeSet{}) + assert.Regexp(t, "FF22075", err) + +} + +func TestABIUnsupportedType(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple", + "internalType": "struct MyType", + "components": [ + { + "name": "fixed", + "type": "fixed256x18" + } + ] + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, _, err = ABItoTypedDataV4(context.Background(), tc) + assert.Regexp(t, "FF22072", err) + +} + +func TestABINestedError(t *testing.T) { + + mailABI := []byte(`{ + "name": "", + "type": "tuple", + "internalType": "struct MyType", + "components": [ + { + "name": "nested1", + "type": "tuple", + "internalType": "struct Nested1", + "components": [ + { + "name": "nested1", + "type": "tuple", + "internalType": "", + "components": [] + } + ] + } + ] + }`) + var abiElem abi.Parameter + err := json.Unmarshal(mailABI, &abiElem) + assert.NoError(t, err) + + tc, err := abiElem.TypeComponentTree() + assert.NoError(t, err) + + _, _, err = ABItoTypedDataV4(context.Background(), tc) + assert.Regexp(t, "FF22075", err) + +} diff --git a/pkg/eip712/typed_data_v4.go b/pkg/eip712/typed_data_v4.go new file mode 100644 index 00000000..02fac454 --- /dev/null +++ b/pkg/eip712/typed_data_v4.go @@ -0,0 +1,349 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package eip712 + +import ( + "bytes" + "context" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-common/pkg/log" + "github.com/hyperledger/firefly-signer/internal/signermsgs" + "github.com/hyperledger/firefly-signer/pkg/abi" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" + "golang.org/x/crypto/sha3" +) + +type TypedData struct { + Types TypeSet `ffstruct:"TypedData" json:"types"` + PrimaryType string `ffstruct:"TypedData" json:"primaryType"` + Domain map[string]interface{} `ffstruct:"TypedData" json:"domain"` + Message map[string]interface{} `ffstruct:"TypedData" json:"message"` +} + +type TypeMember struct { + Name string + Type string +} + +type Type []*TypeMember + +type TypeSet map[string]Type + +const EIP712Domain = "EIP712Domain" + +func EncodeTypedDataV4(ctx context.Context, payload *TypedData) (encoded ethtypes.HexBytes0xPrefix, err error) { + // Add empty EIP712Domain type specification if missing + if payload.Types == nil { + payload.Types = TypeSet{} + } + if _, found := payload.Types[EIP712Domain]; !found { + payload.Types[EIP712Domain] = Type{} + } + if payload.Domain == nil { + payload.Domain = make(map[string]interface{}) + } + if payload.PrimaryType == "" { + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712PrimaryTypeRequired) + } + + // Start with the EIP-712 prefix + buf := new(bytes.Buffer) + buf.Write([]byte{0x19, 0x01}) + + // Encode EIP712Domain from message + domainHash, err := hashStruct(ctx, EIP712Domain, payload.Domain, payload.Types, "domain") + if err != nil { + return nil, err + } + buf.Write(domainHash) + + // If that wasn't the primary type, encode the primary type + if payload.PrimaryType != EIP712Domain { + // Encode the hash + structHash, err := hashStruct(ctx, payload.PrimaryType, payload.Message, payload.Types, "") + if err != nil { + return nil, err + } + buf.Write(structHash) + } + + encoded = buf.Bytes() + log.L(ctx).Tracef("Encoded EIP-712: %s", encoded) + return keccak256(encoded), nil +} + +// A map from type names to types is encoded per encodeType: +// +// > If the struct type references other struct types (and these in turn reference even more struct types), +// > then the set of referenced struct types is collected, sorted by name and appended to the encoding. +func (ts TypeSet) Encode(primaryType string) string { + // Write the primary type first always + buff := new(strings.Builder) + buff.WriteString(ts[primaryType].Encode(primaryType)) + + // Then the reference types sorted by name + referenceTypes := make([]string, 0, len(ts)) + for typeName := range ts { + if typeName != primaryType { + referenceTypes = append(referenceTypes, typeName) + } + } + sort.Strings(referenceTypes) + for _, typeName := range referenceTypes { + buff.WriteString(ts[typeName].Encode(typeName)) + } + return buff.String() +} + +// An individual member is encoded as: +// +// > type ‖ " " ‖ name +func (tm *TypeMember) Encode() string { + return tm.Type + " " + tm.Name +} + +// A type is encoded as: +// +// > name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")" +func (t Type) Encode(name string) string { + buff := new(strings.Builder) + buff.WriteString(name) + buff.WriteRune('(') + for i, tm := range t { + if i > 0 { + buff.WriteRune(',') + } + buff.WriteString(tm.Encode()) + } + buff.WriteRune(')') + return buff.String() +} + +func nextCrumb(breadcrumbs string, name string) string { + if len(breadcrumbs) > 0 { + return breadcrumbs + "." + name + } + return name +} + +func idxCrumb(breadcrumbs string, idx int) string { + return fmt.Sprintf("%s[%d]", breadcrumbs, idx) +} + +func addNestedTypes(typeName string, allTypes TypeSet, typeSet TypeSet) { + // We're not interested in array semantics here + iBracket := strings.Index(typeName, "[") + if iBracket >= 0 { + typeName = typeName[0:iBracket] + } + // See if it's a defined structure type + t, ok := allTypes[typeName] + if ok && typeSet[typeName] == nil { + typeSet[typeName] = t + for _, tm := range t { + addNestedTypes(tm.Type, allTypes, typeSet) + } + } +} + +func keccak256(b []byte) ethtypes.HexBytes0xPrefix { + hash := sha3.NewLegacyKeccak256() + hash.Write(b) + return hash.Sum(nil) +} + +// A map from type names to types is encoded per encodeType: +// +// > If the struct type references other struct types (and these in turn reference even more struct types), +// > then the set of referenced struct types is collected, sorted by name and appended to the encoding. +func encodeType(ctx context.Context, typeName string, allTypes TypeSet) (Type, string, error) { + t := allTypes[typeName] + if t == nil { + return nil, "", i18n.NewError(ctx, signermsgs.MsgEIP712TypeNotFound, typeName) + } + + depSet := make(TypeSet) + addNestedTypes(typeName, allTypes, depSet) + typeEncoded := depSet.Encode(typeName) + log.L(ctx).Tracef("encodeType(%s): %s", typeName, typeEncoded) + return t, typeEncoded, nil +} + +func encodeData(ctx context.Context, typeName string, v interface{}, allTypes TypeSet, breadcrumbs string) (encoded ethtypes.HexBytes0xPrefix, err error) { + // Get the local typeset for the struct and all its deps + t, typeEncoded, err := encodeType(ctx, typeName, allTypes) + if err != nil { + return nil, err + } + // Check the value we have is a map + var vMap map[string]interface{} + switch vt := v.(type) { + case nil: + case map[string]interface{}: + vMap = vt + default: + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712ValueNotMap, breadcrumbs, v) + } + if vMap == nil { + // V4 says the caller writes an empty bytes32, rather than a hash of anything + return nil, nil + } + typeHashed := keccak256([]byte(typeEncoded)) + buf := bytes.NewBuffer(typeHashed) + log.L(ctx).Tracef("hashType(%s): %s", typeName, typeHashed) + // Encode the data of the struct, and write it after the hash of the type + for _, tm := range t { + b, err := encodeElement(ctx, tm.Type, vMap[tm.Name], allTypes, nextCrumb(breadcrumbs, tm.Name)) + if err != nil { + return nil, err + } + buf.Write(b) + } + encoded = buf.Bytes() + log.L(ctx).Tracef("encodeData(%s, %T): %s", typeName, v, encoded) + return encoded, nil +} + +func hashStruct(ctx context.Context, typeName string, v interface{}, allTypes TypeSet, breadcrumbs string) (result ethtypes.HexBytes0xPrefix, err error) { + encoded, err := encodeData(ctx, typeName, v, allTypes, breadcrumbs) + if err != nil { + return nil, err + } + if encoded == nil { + // special rule for a nil value - we don't even include the type info, just return a nil bytes array + bytes32Enc, _ := abiElementaryType(ctx, "bytes32") + encoded, _ = abiEncode(ctx, bytes32Enc, "0x0000000000000000000000000000000000000000000000000000000000000000", breadcrumbs) + result = encoded + } else { + result = keccak256(encoded) + } + log.L(ctx).Tracef("hashStruct(%s): %s", typeName, result) + return result, nil +} + +func encodeElement(ctx context.Context, typeName string, v interface{}, allTypes TypeSet, breadcrumbs string) (ethtypes.HexBytes0xPrefix, error) { + if strings.HasSuffix(typeName, "]") { + // recurse into the array + return hashArray(ctx, typeName, allTypes, v, breadcrumbs) + } else if _, isStruct := allTypes[typeName]; isStruct { + // recurse into the struct + return hashStruct(ctx, typeName, v, allTypes, breadcrumbs) + } + // Need to process based on the Elementary type + tc, err := abiElementaryType(ctx, typeName) + if err != nil { + return nil, err + } + baseType := tc.ElementaryType().BaseType() + switch baseType { + case abi.BaseTypeAddress, abi.BaseTypeBool, abi.BaseTypeInt, abi.BaseTypeUInt: + return abiEncode(ctx, tc, v, breadcrumbs) + case abi.BaseTypeBytes: + // Handle fixed bytes1 to bytes32 + if baseType == abi.BaseTypeBytes && tc.ElementaryFixed() { + return abiEncode(ctx, tc, v, breadcrumbs) + } + // These dynamic bytes/string arrays are special handling, where we need to use the same + // rules as ABI to extract the byte string from the input... but we need to actually + // return a keccak256 of the contents + // - We have special knowledge here that the type will be coercible to []byte + reader := tc.ElementaryType().DataReader() + di, err := reader(ctx, breadcrumbs, v) + if err != nil { + return nil, err + } + return keccak256(di.([]byte)), nil + case abi.BaseTypeString: + reader := tc.ElementaryType().DataReader() + di, err := reader(ctx, breadcrumbs, v) + if err != nil { + return nil, err + } + return keccak256([]byte(di.(string))), nil + default: + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712UnsupportedABIType, tc) + } +} + +func abiElementaryType(ctx context.Context, typeName string) (abi.TypeComponent, error) { + p := &abi.Parameter{Type: typeName} + tc, err := p.TypeComponentTreeCtx(ctx) + if err != nil { + return nil, err + } + if tc.ComponentType() != abi.ElementaryComponent { + return nil, i18n.NewError(ctx, signermsgs.MsgNotElementary, tc) + } + return tc, nil +} + +func abiEncode(ctx context.Context, tc abi.TypeComponent, v interface{}, breadcrumbs string) (b ethtypes.HexBytes0xPrefix, err error) { + // Re-use the ABI function to parse the input value for Elementary types. + // (we weren't able to do this for structs/tuples and arrays, due to EIP-712 specifics) + cv, err := tc.ParseExternalDesc(ctx, v, breadcrumbs) + if err == nil { + b, err = cv.EncodeABIDataCtx(ctx) + } + if err != nil { + return nil, err + } + log.L(ctx).Tracef("encodeElement(%s, %T): %s", tc.String(), v, b) + return b, nil +} + +// hashArray is only called when the last character of the type is `]` +func hashArray(ctx context.Context, typeName string, allTypes TypeSet, v interface{}, breadcrumbs string) (ethtypes.HexBytes0xPrefix, error) { + // Extract the dimension of the array + openPos := strings.LastIndex(typeName, "[") + if openPos <= 0 || typeName[len(typeName)-1] != ']' { + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712InvalidArraySuffix, typeName) + } + dimStr := typeName[openPos+1 : len(typeName)-1] + trimmedTypeName := typeName[0:openPos] + + // We should have an array in the input. + // Note Go JSON unmarshal always gives []interface{}, regardless of type of entry. + va, ok := v.([]interface{}) + if !ok { + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712ValueNotArray, typeName, v) + } + // If we have a fixed dimension, then check we have the right number of elements + if dimStr != "" { + dim, err := strconv.Atoi(dimStr) + if err != nil { + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712InvalidArraySuffix, typeName) + } + if len(va) != dim { + return nil, i18n.NewError(ctx, signermsgs.MsgEIP712InvalidArrayLen, typeName, dim, len(va)) + } + } + // Append all the data + buf := new(bytes.Buffer) + for i, ve := range va { + b, err := encodeElement(ctx, trimmedTypeName, ve, allTypes, idxCrumb(breadcrumbs, i)) + if err != nil { + return nil, err + } + buf.Write(b) + } + return keccak256(buf.Bytes()), nil +} diff --git a/pkg/eip712/typed_data_v4_test.go b/pkg/eip712/typed_data_v4_test.go new file mode 100644 index 00000000..e3e7381c --- /dev/null +++ b/pkg/eip712/typed_data_v4_test.go @@ -0,0 +1,631 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package eip712 + +import ( + "context" + "encoding/json" + "testing" + + "github.com/hyperledger/firefly-common/pkg/ffapi" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +const PersonType = `[{"name": "name","type": "string"},{"name": "wallet","type": "address"}]` + +const MailType = `[{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string"}]` + +func TestMessage_ExampleFromEIP712Spec(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string"}] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "V4", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0xde26f53b35dd5ffdc13f8297e5cc7bbcb1a04bf33803bd2bf4a45eb251360cb8", ed.String()) +} + +func TestMessage_EmptyMessage(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": {}, + "primaryType": "EIP712Domain" + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0x8d4a3f4082945b7879e2b55f181c31a77c8c0a464b70669458abbaaf99de4c38", ed.String()) +} + +func TestMessage_EmptyDomain(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string"}] + }, + "primaryType": "Mail", + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0x25c3d40a39e639a4d0b6e4d2ace5e1281e039c88494d97d8d08f99a6ea75d775", ed.String()) +} + +func TestMessage_NilReference(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string"}] + }, + "primaryType": "Mail", + "message": { + "from": null, + "to": null, + "contents": "Hello, Bob!" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0x326faa52849c078e0e04abe863b29fc28d9d2885d2c4b515fcfb7ba1fac30534", ed.String()) +} + +func TestMessage_BytesString(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "bytes"}] + }, + "primaryType": "Mail", + "message": { + "from": null, + "to": null, + "contents": "0x48656C6C6F2C20426F6221" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0x3e4282c3bc7b7d6df14ef1c1c90f7bef0516134f4ca08d56eb38b061e5632a6b", ed.String()) +} + +func TestMessage_Bytes11(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "bytes11"}] + }, + "primaryType": "Mail", + "message": { + "from": null, + "to": null, + "contents": "0x48656C6C6F2C20426F6221" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0xb13b01acae69dbd0fef3568f1b060a692247aa207609d008f344c8cd7f664220", ed.String()) +} + +func TestMessage_StringArray(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string[]"}] + }, + "primaryType": "Mail", + "message": { + "from": null, + "to": null, + "contents": ["Hello,", "Bob!"] + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0xd0ac411802ea14e4e64eeed229227be1bf2909f0a30bda74c79447dfbf2f5431", ed.String()) +} + +func TestMessage_StringArrayArray(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string[][]"}] + }, + "primaryType": "Mail", + "message": { + "from": null, + "to": null, + "contents": [ + ["Hello,", "Bob!"], + ["How,", "do"] + ] + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0x88454de00616bf6b3697b55281de2e8fb542b3997c397ad70e0c8f8f72d164f0", ed.String()) +} + +func TestMessage_FixedStringArray(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "Person": [{"name": "name","type": "string"},{"name": "wallet","type": "address"}], + "Mail": [{"name": "from","type": "Person"},{"name": "to","type": "Person"},{"name": "contents","type": "string[2]"}] + }, + "primaryType": "Mail", + "message": { + "from": null, + "to": null, + "contents": ["Hello,", "Bob!"] + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0xb1bf2c8635345d6fc3e86e493180f96043548ac761683a4d069725f08a6ea2bf", ed.String()) +} + +func TestMessage_StructArray(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "AllTheTypes": [ + { + "name": "i32", + "type": "int32" + }, + { + "name": "i256", + "type": "int256" + }, + { + "name": "ui32", + "type": "uint32" + }, + { + "name": "ui256", + "type": "uint256" + }, + { + "name": "t", + "type": "bool" + }, + { + "name": "b16", + "type": "bytes16" + }, + { + "name": "b32", + "type": "bytes32" + }, + { + "name": "b", + "type": "bytes" + }, + { + "name": "s", + "type": "string" + } + ] + }, + "primaryType": "AllTheTypes", + "message": { + "i32": -12345, + "i256": "-12345", + "ui32": "0x3039", + "ui256": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "t": true, + "b16": "0x000102030405060708090a0b0c0f0e0f", + "b32": "0x000102030405060708090a0b0c0f0e0f000102030405060708090a0b0c0f0e0f", + "b": "0xfeedbeef", + "s": "Hello World!" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0x651579f58b3a8c79ba668e0f5d83e1c9f6e2715586dc11c62696ec376b595a00", ed.String()) +} + +func TestMissingPrimaryTypeField(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{}`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22080", err) +} + +func TestInvalidDomain(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "EIP712Domain": [{"name":"name","type":"string"}] + }, + "primaryType": "EIP712Domain", + "domain": {"name": ["not","a","string"]} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22032", err) +} + +func TestInvalidPrimaryType(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"name","type":"string"}] + }, + "primaryType": "MyType", + "message": {"name": ["not","a","string"]} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22032", err) +} + +func TestMissingPrimaryType(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType2": [{"name":"name","type":"string"}] + }, + "primaryType": "MyType1", + "message": {"name": "Bob"} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22073", err) +} + +func TestSecondaryTypeNotMap(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType1": [{"name":"t2","type":"MyType2"}], + "MyType2": [{"name":"name","type":"string"}] + }, + "primaryType": "MyType1", + "message": {"t2": "this is not a map"} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22076", err) +} + +func TestTypeInvalid(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"number","type":"int2560"}] + }, + "primaryType": "MyType", + "message": {"number": 12345} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22028", err) +} + +func TestIntValueInvalid(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"number","type":"int256"}] + }, + "primaryType": "MyType", + "message": {"number": "!!! not a number"} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22030", err) +} + +func TestBytesValueInvalid(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"bite","type":"bytes"}] + }, + "primaryType": "MyType", + "message": {"bite": -99} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22034", err) +} + +func TestFixedNotSupported(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"unmovable","type":"fixed256x18"}] + }, + "primaryType": "MyType", + "message": {"unmovable": "1.23"} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22072", err) +} + +func TestTupleNotSupported(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"abi_struct","type":"tuple"}] + }, + "primaryType": "MyType", + "message": {"abi_struct": {}} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22069", err) +} + +func TestAddressInvalid(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"addr","type":"address"}] + }, + "primaryType": "MyType", + "message": {"addr": "not here"} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22034", err) +} + +func TestArrayBadSuffix(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"wonky","type":"int256]"}] + }, + "primaryType": "MyType", + "message": {"wonky": 1} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22077", err) +} + +func TestArrayFieldNonArrayValue(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"many","type":"int256[]"}] + }, + "primaryType": "MyType", + "message": {"many": 1} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22078", err) +} + +func TestArrayFieldBadSize(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"many","type":"int256[n]"}] + }, + "primaryType": "MyType", + "message": {"many": [1]} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22077", err) +} + +func TestArrayFieldSizeMismatch(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"many","type":"int256[2]"}] + }, + "primaryType": "MyType", + "message": {"many": [1]} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22079", err) +} + +func TestArrayValueInvalid(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p TypedData + err := json.Unmarshal([]byte(`{ + "types": { + "MyType": [{"name":"many","type":"int256[]"}] + }, + "primaryType": "MyType", + "message": {"many": ["not a number"]} + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + _, err = EncodeTypedDataV4(ctx, &p) + assert.Regexp(t, "FF22030", err) +} + +func TestTypedDataDocumented(t *testing.T) { + ffapi.CheckObjectDocumented(&TypedData{}) +} diff --git a/pkg/ethsigner/transaction.go b/pkg/ethsigner/transaction.go index 9f5b84a0..191d892f 100644 --- a/pkg/ethsigner/transaction.go +++ b/pkg/ethsigner/transaction.go @@ -52,15 +52,15 @@ const ( ) type Transaction struct { - From json.RawMessage `json:"from,omitempty"` // only here as a possible input to signing key selection (eth_sendTransaction) - Nonce *ethtypes.HexInteger `json:"nonce,omitempty"` - GasPrice *ethtypes.HexInteger `json:"gasPrice,omitempty"` - MaxPriorityFeePerGas *ethtypes.HexInteger `json:"maxPriorityFeePerGas,omitempty"` - MaxFeePerGas *ethtypes.HexInteger `json:"maxFeePerGas,omitempty"` - GasLimit *ethtypes.HexInteger `json:"gas,omitempty"` // note this is required for some methods (eth_estimateGas) - To *ethtypes.Address0xHex `json:"to,omitempty"` - Value *ethtypes.HexInteger `json:"value,omitempty"` - Data ethtypes.HexBytes0xPrefix `json:"data"` + From json.RawMessage `ffstruct:"EthTransaction" json:"from,omitempty"` // only here as a possible input to signing key selection (eth_sendTransaction) + Nonce *ethtypes.HexInteger `ffstruct:"EthTransaction" json:"nonce,omitempty"` + GasPrice *ethtypes.HexInteger `ffstruct:"EthTransaction" json:"gasPrice,omitempty"` + MaxPriorityFeePerGas *ethtypes.HexInteger `ffstruct:"EthTransaction" json:"maxPriorityFeePerGas,omitempty"` + MaxFeePerGas *ethtypes.HexInteger `ffstruct:"EthTransaction" json:"maxFeePerGas,omitempty"` + GasLimit *ethtypes.HexInteger `ffstruct:"EthTransaction" json:"gas,omitempty"` // note this is required for some methods (eth_estimateGas) + To *ethtypes.Address0xHex `ffstruct:"EthTransaction" json:"to,omitempty"` + Value *ethtypes.HexInteger `ffstruct:"EthTransaction" json:"value,omitempty"` + Data ethtypes.HexBytes0xPrefix `ffstruct:"EthTransaction" json:"data"` } func (t *Transaction) BuildLegacy() rlp.List { diff --git a/pkg/ethsigner/transaction_test.go b/pkg/ethsigner/transaction_test.go index 03f66b2d..6f7c8124 100644 --- a/pkg/ethsigner/transaction_test.go +++ b/pkg/ethsigner/transaction_test.go @@ -22,6 +22,7 @@ import ( "math/big" "testing" + "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly-signer/mocks/secp256k1mocks" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/hyperledger/firefly-signer/pkg/rlp" @@ -265,3 +266,7 @@ func TestSignEIP1559Error(t *testing.T) { _, err := txn.SignEIP1559(msn, 12345) assert.Regexp(t, "pop", err) } + +func TestEthTXDocumented(t *testing.T) { + ffapi.CheckObjectDocumented(&Transaction{}) +} diff --git a/pkg/ethsigner/typed_data.go b/pkg/ethsigner/typed_data.go new file mode 100644 index 00000000..63aaeba5 --- /dev/null +++ b/pkg/ethsigner/typed_data.go @@ -0,0 +1,62 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ethsigner + +import ( + "context" + + "github.com/hyperledger/firefly-signer/pkg/eip712" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" + "github.com/hyperledger/firefly-signer/pkg/secp256k1" +) + +type EIP712Result struct { + Hash ethtypes.HexBytes0xPrefix `ffstruct:"EIP712Result" json:"hash"` + SignatureRSV ethtypes.HexBytes0xPrefix `ffstruct:"EIP712Result" json:"signatureRSV"` + V ethtypes.HexInteger `ffstruct:"EIP712Result" json:"v"` + R ethtypes.HexBytes0xPrefix `ffstruct:"EIP712Result" json:"r"` + S ethtypes.HexBytes0xPrefix `ffstruct:"EIP712Result" json:"s"` +} + +func SignTypedDataV4(ctx context.Context, signer secp256k1.SignerDirect, payload *eip712.TypedData) (*EIP712Result, error) { + encodedData, err := eip712.EncodeTypedDataV4(ctx, payload) + if err != nil { + return nil, err + } + // Note that signer.Sign performs the hash + sig, err := signer.SignDirect(encodedData) + if err != nil { + return nil, err + } + + signatureBytes := make([]byte, 65) + sig.R.FillBytes(signatureBytes[0:32]) + sig.S.FillBytes(signatureBytes[32:64]) + signatureBytes[64] = byte(sig.V.Int64()) + + return &EIP712Result{ + Hash: encodedData, + // Include the clearly distinguished V, R & S values of the signature + V: ethtypes.HexInteger(*sig.V), + R: sig.R.FillBytes(make([]byte, 32)), + S: sig.S.FillBytes(make([]byte, 32)), + // the Ethereum convention (which is different to the Golang convention) is to encode compact signatures as + // 65 bytes - R (32B), S (32B), V (1B) + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7294d34c17ca215c201b3772ff67036fa4b1ef12/contracts/utils/cryptography/ECDSA.sol#L56-L73 + SignatureRSV: signatureBytes, + }, nil +} diff --git a/pkg/ethsigner/typed_data_test.go b/pkg/ethsigner/typed_data_test.go new file mode 100644 index 00000000..e29ab83f --- /dev/null +++ b/pkg/ethsigner/typed_data_test.go @@ -0,0 +1,183 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ethsigner + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "testing" + + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/hyperledger/firefly-common/pkg/ffapi" + "github.com/hyperledger/firefly-common/pkg/log" + "github.com/hyperledger/firefly-signer/mocks/secp256k1mocks" + "github.com/hyperledger/firefly-signer/pkg/eip712" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" + "github.com/hyperledger/firefly-signer/pkg/secp256k1" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestSignTypedDataV4(t *testing.T) { + + // We use a simple empty message payload + payload := &eip712.TypedData{ + PrimaryType: eip712.EIP712Domain, + } + keypair, err := secp256k1.GenerateSecp256k1KeyPair() + assert.NoError(t, err) + + ctx := context.Background() + sig, err := SignTypedDataV4(ctx, keypair, payload) + assert.NoError(t, err) + + b, err := json.Marshal(sig) + assert.NoError(t, err) + log.L(context.Background()).Infof("Signature: %s", b) + + foundSig := &secp256k1.SignatureData{ + V: sig.V.BigInt(), + R: new(big.Int), + S: new(big.Int), + } + foundSig.R.SetBytes(sig.R) + foundSig.S.SetBytes(sig.S) + + signaturePayload := ethtypes.HexBytes0xPrefix(sig.Hash) + addr, err := foundSig.RecoverDirect(signaturePayload, -1 /* chain id is in the domain (not applied EIP-155 style to the V value) */) + assert.NoError(t, err) + assert.Equal(t, keypair.Address.String(), addr.String()) + + encoded, err := eip712.EncodeTypedDataV4(ctx, payload) + assert.NoError(t, err) + + // Check all is as we expect + assert.Equal(t, "0x8d4a3f4082945b7879e2b55f181c31a77c8c0a464b70669458abbaaf99de4c38", encoded.String()) + assert.Equal(t, "0x8d4a3f4082945b7879e2b55f181c31a77c8c0a464b70669458abbaaf99de4c38", signaturePayload.String()) +} + +func TestSignTypedDataV4BadPayload(t *testing.T) { + + payload := &eip712.TypedData{ + PrimaryType: "missing", + } + + keypair, err := secp256k1.GenerateSecp256k1KeyPair() + assert.NoError(t, err) + + ctx := context.Background() + _, err = SignTypedDataV4(ctx, keypair, payload) + assert.Regexp(t, "FF22073", err) +} + +func TestSignTypedDataV4SignFail(t *testing.T) { + + payload := &eip712.TypedData{ + PrimaryType: eip712.EIP712Domain, + } + + msn := &secp256k1mocks.SignerDirect{} + msn.On("SignDirect", mock.Anything).Return(nil, fmt.Errorf("pop")) + + ctx := context.Background() + _, err := SignTypedDataV4(ctx, msn, payload) + assert.Regexp(t, "pop", err) +} + +func TestMessage_2(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + var p eip712.TypedData + err := json.Unmarshal([]byte(`{ + "domain": { + "name": "test-app", + "version": "1", + "chainId": 31337, + "verifyingContract": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0" + }, + "types": { + "Issuance": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "to", + "type": "address" + } + ], + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ] + }, + "primaryType": "Issuance", + "message": { + "amount": "1000", + "to": "0xce3a47d24140cca16f8839357ca5fada44a1baef" + } + }`), &p) + assert.NoError(t, err) + + ctx := context.Background() + ed, err := eip712.EncodeTypedDataV4(ctx, &p) + assert.NoError(t, err) + assert.Equal(t, "0xb0132202fa81cafac0e405917f86705728ba02912d185065697cc4ba4e61aec3", ed.String()) + + keys, err := secp256k1.NewSecp256k1KeyPair([]byte(`8d01666832be7eb2dbd57cd3d4410d0231a91533f895de76d0930c689618aefd`)) + assert.NoError(t, err) + assert.Equal(t, "0xbcef501facf72ddacdb055acc2716786ff038728", keys.Address.String()) + + signed, err := SignTypedDataV4(ctx, keys, &p) + assert.NoError(t, err) + + assert.Equal(t, "0xb0132202fa81cafac0e405917f86705728ba02912d185065697cc4ba4e61aec3", signed.Hash.String()) + + // The golang convention is V, R, S for the compact signature (differing from Ethereum's convention of R, S, V) + golangCompactSignature := make([]byte, 65) + golangCompactSignature[0] = signed.SignatureRSV[64] + copy(golangCompactSignature[1:33], signed.SignatureRSV[0:32]) + copy(golangCompactSignature[33:65], signed.SignatureRSV[32:64]) + + fmt.Printf("%s\n", ethtypes.HexBytes0xPrefix(golangCompactSignature)) + fmt.Printf("%s\n", ethtypes.HexBytes0xPrefix(signed.SignatureRSV)) + + pubKey, _, err := ecdsa.RecoverCompact(golangCompactSignature, signed.Hash) + assert.NoError(t, err) + assert.Equal(t, "0xbcef501facf72ddacdb055acc2716786ff038728", secp256k1.PublicKeyToAddress(pubKey).String()) +} + +func TestEIP712ResultDocumented(t *testing.T) { + ffapi.CheckObjectDocumented(&EIP712Result{}) +} diff --git a/pkg/ethsigner/wallet.go b/pkg/ethsigner/wallet.go index 3b6e21de..30d66cf8 100644 --- a/pkg/ethsigner/wallet.go +++ b/pkg/ethsigner/wallet.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -19,6 +19,7 @@ package ethsigner import ( "context" + "github.com/hyperledger/firefly-signer/pkg/eip712" "github.com/hyperledger/firefly-signer/pkg/ethtypes" ) @@ -31,3 +32,8 @@ type Wallet interface { Refresh(ctx context.Context) error Close() error } + +type WalletTypedData interface { + Wallet + SignTypedDataV4(ctx context.Context, from ethtypes.Address0xHex, payload *eip712.TypedData) (*EIP712Result, error) +} diff --git a/pkg/fswallet/fswallet.go b/pkg/fswallet/fswallet.go index db872509..fe32e4d0 100644 --- a/pkg/fswallet/fswallet.go +++ b/pkg/fswallet/fswallet.go @@ -32,6 +32,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-signer/internal/signermsgs" + "github.com/hyperledger/firefly-signer/pkg/eip712" "github.com/hyperledger/firefly-signer/pkg/ethsigner" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/hyperledger/firefly-signer/pkg/keystorev3" @@ -45,7 +46,7 @@ import ( // to the ethsigner.Wallet interface and providing notifications when new // keys are added to the wallet (via FS listener). type Wallet interface { - ethsigner.Wallet + ethsigner.WalletTypedData GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error) AddListener(listener chan<- ethtypes.Address0xHex) } @@ -109,13 +110,21 @@ type fsWallet struct { } func (w *fsWallet) Sign(ctx context.Context, txn *ethsigner.Transaction, chainID int64) ([]byte, error) { - keypair, err := w.getSignerForAccount(ctx, txn.From) + keypair, err := w.getSignerForJSONAccount(ctx, txn.From) if err != nil { return nil, err } return txn.Sign(keypair, chainID) } +func (w *fsWallet) SignTypedDataV4(ctx context.Context, from ethtypes.Address0xHex, payload *eip712.TypedData) (*ethsigner.EIP712Result, error) { + keypair, err := w.getSignerForAddr(ctx, from) + if err != nil { + return nil, err + } + return ethsigner.SignTypedDataV4(ctx, keypair, payload) +} + func (w *fsWallet) Initialize(ctx context.Context) error { // Run a get accounts pass, to check all is ok lCtx, lCancel := context.WithCancel(log.WithLogField(ctx, "fswallet", w.conf.Path)) @@ -233,7 +242,7 @@ func (w *fsWallet) Close() error { return nil } -func (w *fsWallet) getSignerForAccount(ctx context.Context, rawAddrJSON json.RawMessage) (*secp256k1.KeyPair, error) { +func (w *fsWallet) getSignerForJSONAccount(ctx context.Context, rawAddrJSON json.RawMessage) (*secp256k1.KeyPair, error) { // We require an ethereum address in the "from" field var from ethtypes.Address0xHex @@ -241,6 +250,10 @@ func (w *fsWallet) getSignerForAccount(ctx context.Context, rawAddrJSON json.Raw if err != nil { return nil, err } + return w.getSignerForAddr(ctx, from) +} + +func (w *fsWallet) getSignerForAddr(ctx context.Context, from ethtypes.Address0xHex) (*secp256k1.KeyPair, error) { wf, err := w.GetWalletFile(ctx, from) if err != nil { diff --git a/pkg/fswallet/fswallet_test.go b/pkg/fswallet/fswallet_test.go index eca20938..d17b3a93 100644 --- a/pkg/fswallet/fswallet_test.go +++ b/pkg/fswallet/fswallet_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/hyperledger/firefly-common/pkg/config" + "github.com/hyperledger/firefly-signer/pkg/eip712" "github.com/hyperledger/firefly-signer/pkg/ethsigner" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/sirupsen/logrus" @@ -83,7 +84,7 @@ func TestGetAccountSimpleFilenamesOK(t *testing.T) { ctx, f, done := newTestRegexpFilenameOnlyWallet(t, true) defer done() - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.NoError(t, err) } @@ -95,7 +96,7 @@ func TestGetAccountSimpleFilenamesMissingPWD(t *testing.T) { f.conf.Filenames.PasswordExt = ".wrong" - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } @@ -105,7 +106,7 @@ func TestGetAccountSimpleFilenamesMismatchAddress(t *testing.T) { ctx, f, done := newTestRegexpFilenameOnlyWallet(t, true) defer done() - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"abcd1234abcd1234abcd1234abcd1234abcd1234"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"abcd1234abcd1234abcd1234abcd1234abcd1234"`)) assert.Regexp(t, "FF22059", err) } @@ -125,11 +126,11 @@ func TestListAccountsTOMLOk(t *testing.T) { assert.True(t, all["0x497eedc4299dea2f2a364be10025d0ad0f702de3"]) assert.True(t, all["0x5d093e9b41911be5f5c4cf91b108bac5d130fa83"]) - _, err = f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.NoError(t, err) - _, err = f.getSignerForAccount(ctx, json.RawMessage(`"0x497eedc4299dea2f2a364be10025d0ad0f702de3"`)) + _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x497eedc4299dea2f2a364be10025d0ad0f702de3"`)) assert.Regexp(t, "FF22015", err) - _, err = f.getSignerForAccount(ctx, json.RawMessage(`"0x5d093e9b41911be5f5c4cf91b108bac5d130fa83"`)) + _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x5d093e9b41911be5f5c4cf91b108bac5d130fa83"`)) assert.Regexp(t, "FF22015", err) } @@ -216,6 +217,19 @@ func TestSignOK(t *testing.T) { } +func TestSignTypedDataOK(t *testing.T) { + + ctx, f, done := newTestTOMLMetadataWallet(t, true) + defer done() + + b, err := f.SignTypedDataV4(ctx, *ethtypes.MustNewAddress(`0x1f185718734552d08278aa70f804580bab5fd2b4`), &eip712.TypedData{ + PrimaryType: eip712.EIP712Domain, + }) + assert.NoError(t, err) + assert.NotNil(t, b) + +} + func TestSignNotFound(t *testing.T) { ctx, f, done := newTestTOMLMetadataWallet(t, true) @@ -228,17 +242,29 @@ func TestSignNotFound(t *testing.T) { } +func TestSignTypedDataNotFound(t *testing.T) { + + ctx, f, done := newTestTOMLMetadataWallet(t, true) + defer done() + + _, err := f.SignTypedDataV4(ctx, *ethtypes.MustNewAddress(`0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`), &eip712.TypedData{ + PrimaryType: eip712.EIP712Domain, + }) + assert.Regexp(t, "FF22014", err) + +} + func TestGetAccountCached(t *testing.T) { ctx, f, done := newTestTOMLMetadataWallet(t, true) defer done() - s, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + s, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.NoError(t, err) assert.NotNil(t, s) // 2nd time is cached - s, err = f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + s, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.NoError(t, err) assert.NotNil(t, s) @@ -250,7 +276,7 @@ func TestGetAccountBadYAML(t *testing.T) { defer done() f.conf.Metadata.Format = "yaml" - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } @@ -260,7 +286,7 @@ func TestGetAccountBadAddress(t *testing.T) { ctx, f, done := newTestTOMLMetadataWallet(t, true) defer done() - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"bad address"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"bad address"`)) assert.Regexp(t, "bad address", err) } @@ -271,7 +297,7 @@ func TestGetAccountBadJSON(t *testing.T) { defer done() f.conf.Metadata.Format = "json" - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } @@ -319,7 +345,7 @@ func TestGetAccountBadTOMLRefKey(t *testing.T) { assert.NoError(t, err) f := ff.(*fsWallet) - _, err = f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22014", err) } @@ -336,7 +362,7 @@ func TestGetAccountNoTemplates(t *testing.T) { assert.NoError(t, err) f := ff.(*fsWallet) - _, err = f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22014", err) } @@ -348,7 +374,7 @@ func TestGetAccountBadKeyfile(t *testing.T) { f.metadataPasswordFileProperty = nil f.conf.DefaultPasswordFile = "../../test/keystore_toml/1f185718734552d08278aa70f804580bab5fd2b4.pwd" - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } @@ -361,7 +387,7 @@ func TestGetAccountBadDefaultPasswordfile(t *testing.T) { f.metadataPasswordFileProperty = nil f.conf.DefaultPasswordFile = "!!!" - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } @@ -372,7 +398,7 @@ func TestGetAccountNoPassword(t *testing.T) { defer done() f.metadataPasswordFileProperty = nil - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } @@ -383,7 +409,7 @@ func TestGetAccountWrongPath(t *testing.T) { defer done() f.metadataPasswordFileProperty = nil - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"5d093e9b41911be5f5c4cf91b108bac5d130fa83"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"5d093e9b41911be5f5c4cf91b108bac5d130fa83"`)) assert.Regexp(t, "FF22015", err) } @@ -396,7 +422,7 @@ func TestGetAccountNotFound(t *testing.T) { f.metadataPasswordFileProperty = nil f.conf.DefaultPasswordFile = "!!!" - _, err := f.getSignerForAccount(ctx, json.RawMessage(`"0xFFFF5718734552d08278aa70f804580bab5fd2b4"`)) + _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0xFFFF5718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22014", err) } diff --git a/pkg/rpcbackend/backend.go b/pkg/rpcbackend/backend.go index 23f19020..5e3f796a 100644 --- a/pkg/rpcbackend/backend.go +++ b/pkg/rpcbackend/backend.go @@ -199,8 +199,16 @@ func (rc *RPCClient) SyncRequest(ctx context.Context, rpcReq *RPCRequest) (rpcRe } // JSON/RPC allows errors to be returned with a 200 status code, as well as other status codes if res.IsError() || rpcRes.Error != nil && rpcRes.Error.Code != 0 { - log.L(ctx).Errorf("RPC[%s] <-- [%d]: %s", rpcTraceID, res.StatusCode(), rpcRes.Message()) - err := fmt.Errorf(rpcRes.Message()) + rpcMsg := rpcRes.Message() + errLog := rpcMsg + if rpcMsg == "" { + // Log the raw result in the case of JSON parse error etc. (note that Resty no longer + // returns this as an error - rather the body comes back raw) + errLog = string(res.Body()) + rpcMsg = i18n.NewError(ctx, signermsgs.MsgRPCRequestFailed, res.Status()).Error() + } + log.L(ctx).Errorf("RPC[%s] <-- [%d]: %s", rpcTraceID, res.StatusCode(), errLog) + err := fmt.Errorf(rpcMsg) return rpcRes, err } log.L(ctx).Infof("RPC[%s] <-- %s [%d] OK (%.2fms)", rpcTraceID, rpcReq.Method, res.StatusCode(), float64(time.Since(rpcStartTime))/float64(time.Millisecond)) diff --git a/pkg/secp256k1/signer.go b/pkg/secp256k1/signer.go index 4094ed01..dd76ef2b 100644 --- a/pkg/secp256k1/signer.go +++ b/pkg/secp256k1/signer.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -33,7 +33,12 @@ type SignatureData struct { // Signer is the low level common interface that can be implemented by any module which provides signature capability type Signer interface { - Sign(message []byte) (*SignatureData, error) + Sign(msgToHashAndSign []byte) (*SignatureData, error) +} + +type SignerDirect interface { + Signer + SignDirect(message []byte) (*SignatureData, error) } // getVNormalized returns the original 27/28 parity @@ -67,10 +72,16 @@ func (s *SignatureData) UpdateEIP2930() { s.V = s.V.Sub(s.V, big.NewInt(27)) } -// Recover obtains the original signer +// Recover obtains the original signer from the hash of the message func (s *SignatureData) Recover(message []byte, chainID int64) (a *ethtypes.Address0xHex, err error) { msgHash := sha3.NewLegacyKeccak256() msgHash.Write(message) + return s.RecoverDirect(msgHash.Sum(nil), chainID) +} + +// Recover obtains the original signer +func (s *SignatureData) RecoverDirect(message []byte, chainID int64) (a *ethtypes.Address0xHex, err error) { + signatureBytes := make([]byte, 65) signatureBytes[0], err = s.getVNormalized(chainID) if err != nil { @@ -78,21 +89,27 @@ func (s *SignatureData) Recover(message []byte, chainID int64) (a *ethtypes.Addr } s.R.FillBytes(signatureBytes[1:33]) s.S.FillBytes(signatureBytes[33:65]) - pubKey, _, err := ecdsa.RecoverCompact(signatureBytes, msgHash.Sum(nil)) // uses S256() by default + pubKey, _, err := ecdsa.RecoverCompact(signatureBytes, message) // uses S256() by default if err != nil { return nil, err } return PublicKeyToAddress(pubKey), nil } -// Sign performs raw signing - give legacy 27/28 V values +// Sign hashes the input then signs it func (k *KeyPair) Sign(message []byte) (ethSig *SignatureData, err error) { + msgHash := sha3.NewLegacyKeccak256() + msgHash.Write(message) + hashed := msgHash.Sum(nil) + return k.SignDirect(hashed) +} + +// SignDirect performs raw signing - give legacy 27/28 V values +func (k *KeyPair) SignDirect(message []byte) (ethSig *SignatureData, err error) { if k == nil { return nil, fmt.Errorf("nil signer") } - msgHash := sha3.NewLegacyKeccak256() - msgHash.Write(message) - sig, err := ecdsa.SignCompact(k.PrivateKey, msgHash.Sum(nil), false) // uses S256() by default + sig, err := ecdsa.SignCompact(k.PrivateKey, message, false) // uses S256() by default if err == nil { // btcec does all the hard work for us. However, the interface of btcec is such // that we need to unpack the result for Ethereum encoding.