op-signer: add to this repo (#51)

* op-signer: add to this repo

* circleci: add op-signer jobs/workflows

* ops: update tag service to include op-signer

* readme: add op-signer one sentence description

* ci: add op-signer option to github action

* ops: add op-signer min version
This commit is contained in:
Sam Stokes 2024-09-10 15:01:09 -04:00 committed by GitHub
parent 7f8095c9ad
commit 75b02dff3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 2502 additions and 0 deletions

@ -16,6 +16,7 @@ workflows:
# <regex path-to-test> <parameter-to-set> <value-of-pipeline-parameter>
mapping: |
op-conductor-mon/.* run-build-op-conductor-mon true
op-signer/.* run-build-op-signer true
op-ufm/.* run-build-op-ufm true
proxyd/.* run-build-proxyd true
.circleci/.* run-all true

@ -10,6 +10,9 @@ parameters:
run-build-op-conductor-mon:
type: boolean
default: false
run-build-op-signer:
type: boolean
default: false
run-build-op-ufm:
type: boolean
default: false
@ -76,6 +79,7 @@ jobs:
command: |
echo "Configuration Results:"
echo "run-build-op-conductor-mon: << pipeline.parameters.run-build-op-conductor-mon >>"
echo "run-build-op-signer: << pipeline.parameters.run-build-op-signer >>"
echo "run-build-op-ufm: << pipeline.parameters.run-build-op-ufm >>"
echo "run-build-proxyd: << pipeline.parameters.run-build-proxyd >>"
echo "run-all: << pipeline.parameters.run-all >>"
@ -100,6 +104,12 @@ jobs:
echo "op-conductor-mon tag regex match: false"
fi
if [[ $CURRENT_TAG =~ ^op-signer/v.* ]]; then
echo "op-signer tag regex match: true"
else
echo "op-signer tag regex match: false"
fi
if [[ $CURRENT_TAG =~ ^op-ufm/v.* ]]; then
echo "op-ufm tag regex match: true"
else
@ -408,6 +418,22 @@ workflows:
docker_name: op-conductor-mon
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
docker_context: .
op-signer:
when:
or: [<< pipeline.parameters.run-build-op-signer >>, << pipeline.parameters.run-all >>]
jobs:
- go-lint:
name: op-signer-lint
module: op-signer
- go-test:
name: op-signer-tests
module: op-signer
- docker-build:
name: op-signer-docker-build
docker_file: op-signer/Dockerfile
docker_name: op-signer
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
docker_context: .
op-ufm:
when:
or: [<< pipeline.parameters.run-build-op-ufm >>, << pipeline.parameters.run-all >>]
@ -455,6 +481,38 @@ workflows:
only: /^(proxyd|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/
branches:
ignore: /.*/
- docker-build:
name: op-signer-docker-build
filters:
tags:
only: /^op-signer\/v.*/
docker_name: op-signer
docker_tags: <<pipeline.git.revision>>
docker_context: .
docker_file: op-signer/Dockerfile
context:
- oplabs-gcr-release
requires:
- hold
- docker-publish:
name: op-signer-docker-publish
docker_name: op-signer
docker_tags: <<pipeline.git.revision>>
context:
- oplabs-gcr-release
requires:
- op-signer-docker-build
- docker-tag-op-stack-release:
name: docker-tag-op-signer-release
filters:
tags:
only: /^op-signer\/v.*/
branches:
ignore: /.*/
context:
- oplabs-gcr-release
requires:
- op-signer-docker-publish
- docker-build:
name: op-ufm-docker-build
filters:

@ -18,6 +18,7 @@ on:
required: true
type: choice
options:
- op-signer
- op-ufm
- proxyd
prerelease:

@ -4,4 +4,5 @@ This repository is an extension of the [Optimism monorepo](https://github.com/et
## Components
- op-conductor-mon: Monitors multiple op-conductor instances and provides a unified interface for reporting metrics.
- op-signer: Thin gateway that supports `eth_signTransaction` RPC endpoint to sign ethereum tx payloads using private key stored in KMS
- op-ufm: User facing monitoring creates transactions at regular intervals and observe transaction propagation across different RPC providers.

37
op-signer/.air.toml Normal file

@ -0,0 +1,37 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "bin"
[build]
args_bin = []
bin = "./bin/op-signer"
cmd = "make build"
delay = 1000
exclude_dir = ["bin"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false

4
op-signer/.envrc Normal file

@ -0,0 +1,4 @@
export OP_SIGNER_LOG_LEVEL=debug
export OP_SIGNER_LOG_COLOR=true
export OP_SIGNER_RPC_PORT=8080
export OP_SIGNER_METRICS_ENABLED=true

2
op-signer/.gitignore vendored Normal file

@ -0,0 +1,2 @@
bin/
tls/

1
op-signer/CHANGELOG.md Normal file

@ -0,0 +1 @@
# @eth-optimism/signer

18
op-signer/Dockerfile Normal file

@ -0,0 +1,18 @@
FROM golang:1.21.3-alpine3.18 as builder
COPY ./op-signer /app
WORKDIR /app
RUN apk --no-cache add make jq bash git alpine-sdk
RUN make build
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
RUN addgroup -S app && adduser -S app -G app
USER app
WORKDIR /app
COPY --from=builder /app/bin/op-signer /app
ENTRYPOINT ["/app/op-signer"]

37
op-signer/Makefile Normal file

@ -0,0 +1,37 @@
GITCOMMIT := $(shell git rev-parse HEAD)
GITDATE := $(shell git show -s --format='%ct')
VERSION := v0.0.0
LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.Version=$(VERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
all: build
docker:
docker build ../ -f Dockerfile -t op-signer:latest
build:
env GO111MODULE=on go build -v $(LDFLAGS) -o ./bin/op-signer ./cmd
clean:
rm ./bin/op-signer
generate:
[ '$(shell mockgen --version)' = 'v1.6.0' ] || go install github.com/golang/mock/mockgen@v1.6.0
go generate ./...
test: generate
go test -v ./...
lint:
golangci-lint run ./...
.PHONY: \
build \
clean \
test \
generate \
lint \
docker

14
op-signer/README.md Normal file

@ -0,0 +1,14 @@
# @eth-optimism/signer
Signer service and client library
## Setup
Install go1.18
```bash
make build
source .env.example # (or copy to .envrc if using direnv)
./bin/signer
```

239
op-signer/app.go Normal file

@ -0,0 +1,239 @@
package app
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
"sync/atomic"
"github.com/prometheus/client_golang/prometheus"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/optimism/op-service/httputil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/tls/certman"
"github.com/ethereum-optimism/infra/op-signer/client"
"github.com/ethereum-optimism/infra/op-signer/service"
)
type SignerApp struct {
log log.Logger
version string
pprofServer *oppprof.Service
metricsServer *httputil.HTTPServer
registry *prometheus.Registry
signer *service.SignerService
rpc *oprpc.Server
stopped atomic.Bool
}
func InitFromConfig(ctx context.Context, log log.Logger, cfg *Config, version string) (*SignerApp, error) {
if err := cfg.Check(); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
app := &SignerApp{log: log, version: version}
if err := app.init(cfg); err != nil {
return nil, errors.Join(err, app.Stop(ctx)) // clean up the failed init attempt
}
return app, nil
}
func (s *SignerApp) init(cfg *Config) error {
if err := s.initPprof(cfg); err != nil {
return fmt.Errorf("pprof error: %w", err)
}
if err := s.initMetrics(cfg); err != nil {
return fmt.Errorf("metrics error: %w", err)
}
if err := s.initRPC(cfg); err != nil {
return fmt.Errorf("metrics error: %w", err)
}
return nil
}
func (s *SignerApp) initPprof(cfg *Config) error {
if !cfg.PprofConfig.ListenEnabled {
return nil
}
s.pprofServer = oppprof.New(
cfg.PprofConfig.ListenEnabled,
cfg.PprofConfig.ListenAddr,
cfg.PprofConfig.ListenPort,
cfg.PprofConfig.ProfileType,
cfg.PprofConfig.ProfileDir,
cfg.PprofConfig.ProfileFilename,
)
s.log.Info("Starting pprof server", "addr", cfg.PprofConfig.ListenAddr, "port", cfg.PprofConfig.ListenPort)
if err := s.pprofServer.Start(); err != nil {
return fmt.Errorf("failed to start pprof server: %w", err)
}
return nil
}
func (s *SignerApp) initMetrics(cfg *Config) error {
registry := opmetrics.NewRegistry()
registry.MustRegister(service.MetricSignTransactionTotal)
s.registry = registry // some things require metrics registry
if !cfg.MetricsConfig.Enabled {
return nil
}
metricsCfg := cfg.MetricsConfig
s.log.Info("Starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
metricsServer, err := opmetrics.StartServer(registry, metricsCfg.ListenAddr, metricsCfg.ListenPort)
if err != nil {
return fmt.Errorf("failed to start metrics server: %w", err)
}
s.log.Info("Started metrics server", "endpoint", metricsServer.Addr())
s.metricsServer = metricsServer
return nil
}
func (s *SignerApp) initRPC(cfg *Config) error {
caCert, err := os.ReadFile(cfg.TLSConfig.TLSCaCert)
if err != nil {
return fmt.Errorf("failed to read tls ca cert: %s", string(caCert))
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
cm, err := certman.New(s.log, cfg.TLSConfig.TLSCert, cfg.TLSConfig.TLSKey)
if err != nil {
return fmt.Errorf("failed to read tls cert or key: %w", err)
}
if err := cm.Watch(); err != nil {
return fmt.Errorf("failed to start certman watcher: %w", err)
}
tlsConfig := &tls.Config{
GetCertificate: cm.GetCertificate,
ClientCAs: caCertPool,
ClientAuth: tls.VerifyClientCertIfGiven, // necessary for k8s healthz probes, but we check the cert in service/auth.go
}
serverTlsConfig := &oprpc.ServerTLSConfig{
Config: tlsConfig,
CLIConfig: &cfg.TLSConfig,
}
rpcCfg := cfg.RPCConfig
s.rpc = oprpc.NewServer(
rpcCfg.ListenAddr,
rpcCfg.ListenPort,
s.version,
oprpc.WithLogger(s.log),
oprpc.WithTLSConfig(serverTlsConfig),
oprpc.WithMiddleware(service.NewAuthMiddleware()),
oprpc.WithHTTPRecorder(opmetrics.NewPromHTTPRecorder(s.registry, "signer")),
)
serviceCfg, err := service.ReadConfig(cfg.ServiceConfigPath)
if err != nil {
return fmt.Errorf("failed to read service config: %w", err)
}
s.signer = service.NewSignerService(s.log, serviceCfg)
s.signer.RegisterAPIs(s.rpc)
if err := s.rpc.Start(); err != nil {
return fmt.Errorf("error starting RPC server: %w", err)
}
s.log.Info("Started op-signer RPC server", "addr", s.rpc.Endpoint())
return nil
}
func (s *SignerApp) Start(ctx context.Context) error {
return nil
}
func (s *SignerApp) Stop(ctx context.Context) error {
var result error
if s.rpc != nil {
if err := s.rpc.Stop(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err))
}
}
if s.pprofServer != nil {
if err := s.pprofServer.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop pprof server: %w", err))
}
}
if s.metricsServer != nil {
if err := s.metricsServer.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err))
}
}
return result
}
func (s *SignerApp) Stopped() bool {
return s.stopped.Load()
}
var _ cliapp.Lifecycle = (*SignerApp)(nil)
func MainAppAction(version string) cliapp.LifecycleAction {
return func(cliCtx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) {
cfg := NewConfig(cliCtx)
logger := oplog.NewLogger(cliCtx.App.Writer, cfg.LogConfig)
return InitFromConfig(cliCtx.Context, logger, cfg, version)
}
}
func ClientSign(version string) func(cliCtx *cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(os.Stdout, cfg.LogConfig)
log.Root().SetHandler(l.GetHandler())
txarg := cliCtx.Args().First()
if txarg == "" {
return errors.New("no transaction argument was provided")
}
txraw, err := hexutil.Decode(txarg)
if err != nil {
return errors.New("failed to decode transaction argument")
}
client, err := client.NewSignerClient(l, cfg.ClientEndpoint, cfg.TLSConfig)
if err != nil {
return err
}
tx := &types.Transaction{}
if err := tx.UnmarshalBinary(txraw); err != nil {
return fmt.Errorf("failed to unmarshal transaction argument: %w", err)
}
tx, err = client.SignTransaction(context.Background(), tx)
if err != nil {
return err
}
result, _ := tx.MarshalJSON()
fmt.Println(string(result))
return nil
}
}

@ -0,0 +1,91 @@
package client
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-service/signer"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
)
type SignerClient struct {
client *rpc.Client
status string
logger log.Logger
}
func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConfig) (*SignerClient, error) {
caCert, err := os.ReadFile(tlsConfig.TLSCaCert)
if err != nil {
return nil, fmt.Errorf("failed to read tls.ca: %w", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
cert, err := tls.LoadX509KeyPair(tlsConfig.TLSCert, tlsConfig.TLSKey)
if err != nil {
return nil, fmt.Errorf("failed to read tls.cert or tls.key: %w", err)
}
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
},
},
}
rpcClient, err := rpc.DialOptions(context.Background(), endpoint, rpc.WithHTTPClient(httpClient))
if err != nil {
return nil, err
}
signerClient := &SignerClient{logger: logger, client: rpcClient}
// Check if reachable
version, err := signerClient.pingVersion()
if err != nil {
return nil, err
}
signerClient.status = fmt.Sprintf("ok [version=%v]", version)
return signerClient, nil
}
func (s *SignerClient) pingVersion() (string, error) {
var v string
if err := s.client.Call(&v, "health_status"); err != nil {
return "", err
}
return v, nil
}
func (s *SignerClient) SignTransaction(
ctx context.Context,
tx *types.Transaction,
) (*types.Transaction, error) {
args := signer.NewTransactionArgsFromTransaction(tx.ChainId(), nil, tx)
var result hexutil.Bytes
if err := s.client.Call(&result, "eth_signTransaction", args); err != nil {
return nil, fmt.Errorf("eth_signTransaction failed: %w", err)
}
signed := &types.Transaction{}
if err := signed.UnmarshalBinary(result); err != nil {
return nil, err
}
return signed, nil
}

51
op-signer/cmd/main.go Normal file

@ -0,0 +1,51 @@
package main
import (
"fmt"
"os"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log"
signer "github.com/ethereum-optimism/infra/op-signer"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
)
var (
Version = ""
GitCommit = ""
GitDate = ""
)
func main() {
oplog.SetupDefaults()
app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(signer.CLIFlags("OP_SIGNER"))
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate)
app.Name = "op-signer"
app.Usage = "OP Signing Service"
app.Description = ""
app.Commands = []*cli.Command{
{
Name: "client",
Usage: "test client for signer service",
Subcommands: []*cli.Command{
{
Name: "sign",
Usage: "sign a transaction",
Action: signer.ClientSign(Version),
Flags: cliapp.ProtectFlags(signer.ClientSignCLIFlags("SIGNER")),
},
},
},
}
app.Action = cliapp.LifecycleCmd(signer.MainAppAction(Version))
err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}

85
op-signer/config.go Normal file

@ -0,0 +1,85 @@
package app
import (
"github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
)
const (
ServiceConfigPathFlagName = "config"
ClientEndpointFlagName = "endpoint"
)
func CLIFlags(envPrefix string) []cli.Flag {
flags := []cli.Flag{
&cli.StringFlag{
Name: ServiceConfigPathFlagName,
Usage: "Signer service configuration file path",
Value: "config.yaml",
EnvVars: opservice.PrefixEnvVar(envPrefix, "SERVICE_CONFIG"),
},
}
flags = append(flags, oprpc.CLIFlags(envPrefix)...)
flags = append(flags, oplog.CLIFlags(envPrefix)...)
flags = append(flags, opmetrics.CLIFlags(envPrefix)...)
flags = append(flags, oppprof.CLIFlags(envPrefix)...)
flags = append(flags, optls.CLIFlags(envPrefix)...)
return flags
}
func ClientSignCLIFlags(envPrefix string) []cli.Flag {
flags := []cli.Flag{
&cli.StringFlag{
Name: ClientEndpointFlagName,
Usage: "Signer endpoint the client will connect to",
Value: "http://localhost:8080",
EnvVars: opservice.PrefixEnvVar(envPrefix, "CLIENT_ENDPOINT"),
},
}
return flags
}
type Config struct {
ClientEndpoint string
ServiceConfigPath string
TLSConfig optls.CLIConfig
RPCConfig oprpc.CLIConfig
LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig
}
func (c Config) Check() error {
if err := c.RPCConfig.Check(); err != nil {
return err
}
if err := c.MetricsConfig.Check(); err != nil {
return err
}
if err := c.PprofConfig.Check(); err != nil {
return err
}
if err := c.TLSConfig.Check(); err != nil {
return err
}
return nil
}
func NewConfig(ctx *cli.Context) *Config {
return &Config{
ClientEndpoint: ctx.String(ClientEndpointFlagName),
ServiceConfigPath: ctx.String(ServiceConfigPathFlagName),
TLSConfig: optls.ReadCLIConfig(ctx),
RPCConfig: oprpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx),
}
}

3
op-signer/config.yaml Normal file

@ -0,0 +1,3 @@
auth:
- name: localhost
key: projects/my-gcp-project/locations/my-region/keyRings/my-ring/cryptoKeys/my-key/cryptoKeyVersions/1

51
op-signer/gen-local-tls.sh Executable file

@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
TLS_DIR=$SCRIPT_DIR/tls
version=$(openssl version)
if [[ "$version" != "LibreSSL"* ]] && [[ "$version" != "OpenSSL 1.1"* ]]; then
echo "openssl version: $version"
echo "script only works with LibreSSL (darwin) or OpenSSL 1.1*"
exit 1
fi
echo "Generating mTLS credentials for local development..."
echo ""
mkdir -p "$TLS_DIR"
if [ ! -f "$TLS_DIR/ca.crt" ]; then
echo 'Generating CA'
openssl req -newkey rsa:2048 \
-new -nodes -x509 \
-days 365 \
-sha256 \
-out "$TLS_DIR/ca.crt" \
-keyout "$TLS_DIR/ca.key" \
-subj "/O=OP Labs/CN=root"
fi
echo 'Generating TLS certificate request'
openssl genrsa -out "$TLS_DIR/tls.key" 2048
openssl req -new -key "$TLS_DIR/tls.key" \
-days 1 \
-sha256 \
-out "$TLS_DIR/tls.csr" \
-keyout "$TLS_DIR/tls.key" \
-subj "/O=OP Labs/CN=localhost" \
-extensions san \
-config <(echo '[req]'; echo 'distinguished_name=req'; \
echo '[san]'; echo 'subjectAltName=DNS:localhost')
openssl x509 -req -in "$TLS_DIR/tls.csr" \
-sha256 \
-CA "$TLS_DIR/ca.crt" \
-CAkey "$TLS_DIR/ca.key" \
-CAcreateserial \
-out "$TLS_DIR/tls.crt" \
-days 3 \
-extfile <(echo 'subjectAltName=DNS:localhost')

115
op-signer/go.mod Normal file

@ -0,0 +1,115 @@
module github.com/ethereum-optimism/infra/op-signer
go 1.21
require (
cloud.google.com/go/kms v1.10.1
github.com/ethereum-optimism/optimism v1.4.3-0.20240125020216-6a3b1ec12399
github.com/ethereum/go-ethereum v1.13.5
github.com/golang/mock v1.6.0
github.com/googleapis/gax-go v1.0.3
github.com/googleapis/gax-go/v2 v2.11.0
github.com/holiman/uint256 v1.2.3
github.com/prometheus/client_golang v1.18.0
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.27.1
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/DataDog/zstd v1.5.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/VictoriaMetrics/fastcache v1.12.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.7.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.12.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a // indirect
github.com/ethereum/c-kzg-4844 v0.4.0 // indirect
github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/go-bexpr v0.1.11 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.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/rivo/uniseg v0.4.3 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/cors v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.1 // indirect
google.golang.org/api v0.126.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/grpc v1.55.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)
replace github.com/ethereum/go-ethereum v1.13.5 => github.com/ethereum-optimism/op-geth v1.101305.3-rc.1.0.20240124221225-5c6f10d449ab

496
op-signer/go.sum Normal file

@ -0,0 +1,496 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/kms v1.10.1 h1:7hm1bRqGCA1GBRQUrp831TwJ9TWhP+tvLuP497CQS2g=
cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40=
github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
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/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo=
github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash/v2 v2.1.1/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=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4 h1:PuHFhOUMnD62r80dN+Ik5qco2drekgsUSVdcHsvllec=
github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA=
github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v1.101305.3-rc.1.0.20240124221225-5c6f10d449ab h1:SpoQiEG5mteZnaPCo5Pm5QrberdODUefwLjdAtL9LAE=
github.com/ethereum-optimism/op-geth v1.101305.3-rc.1.0.20240124221225-5c6f10d449ab/go.mod h1:/0cPafbmmt3JR0yiuoBJWTRCx8briXUd/mtXr+GJ3Zw=
github.com/ethereum-optimism/optimism v1.4.3-0.20240125020216-6a3b1ec12399 h1:b3I8SKmsLaAJn4TzGpKLzCGBIRsB82f1B7trMZk2+qU=
github.com/ethereum-optimism/optimism v1.4.3-0.20240125020216-6a3b1ec12399/go.mod h1:VGQGQ38ORldOlb3ifKYacmaCLQuVToCj3jax7RjeXys=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a h1:mWIRpGyrAlWHUznUHKgAJUafkNGfO7VmeLjilhVhB80=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk=
github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY=
github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
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/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go v1.0.3 h1:9dMLqhaibYONnDRcnHdUs9P8Mw64jLlZTYlDe3leBtQ=
github.com/googleapis/gax-go v1.0.3/go.mod h1:QyXYajJFdARxGzjwUfbDFIse7Spkw81SJ4LrBJXtlQ8=
github.com/googleapis/gax-go/v2 v2.0.2/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY=
github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE=
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw=
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o=
github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
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/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw=
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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 v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
github.com/stretchr/objx v0.1.0/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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/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/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190221220918-438050ddec5e/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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-20180906233101-161cd47e91fd/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=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
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=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o=
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
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/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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=

44
op-signer/service/auth.go Normal file

@ -0,0 +1,44 @@
package service
import (
"context"
"net/http"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
)
type ClientInfo struct {
ClientName string
}
type clientInfoContextKey struct{}
func NewAuthMiddleware() oprpc.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientInfo := ClientInfo{}
// PeerTLSInfo is attached to context by upstream op-service middleware
peerTlsInfo := optls.PeerTLSInfoFromContext(r.Context())
if peerTlsInfo.LeafCertificate == nil {
http.Error(w, "client certificate was not provided", 401)
return
}
// Note that the certificate is already verified by http server if we get here
if len(peerTlsInfo.LeafCertificate.DNSNames) < 1 {
http.Error(w, "client certificate verified but did not contain DNS SAN extension", 401)
return
}
clientInfo.ClientName = peerTlsInfo.LeafCertificate.DNSNames[0]
ctx := context.WithValue(r.Context(), clientInfoContextKey{}, clientInfo)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func ClientInfoFromContext(ctx context.Context) ClientInfo {
info, _ := ctx.Value(clientInfoContextKey{}).(ClientInfo)
return info
}

@ -0,0 +1,63 @@
package service
import (
"crypto/tls"
"crypto/x509"
"net/http"
"net/http/httptest"
"testing"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
"github.com/stretchr/testify/assert"
)
func TestAuthMiddleware_NoCertificate(t *testing.T) {
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.NotNil(t, nil, "handler should not have been invoked")
})
handler = NewAuthMiddleware()(handler)
handler = optls.NewPeerTLSMiddleware(handler)
assert.HTTPStatusCode(t, handler.ServeHTTP, "GET", "/", nil, 401)
}
func TestAuthMiddleware_CertificateMissingDNS(t *testing.T) {
req, _ := http.NewRequest("GET", "/", nil)
req.TLS = &tls.ConnectionState{PeerCertificates: []*x509.Certificate{}}
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.NotNil(t, nil, "handler should not have been invoked")
})
handler = NewAuthMiddleware()(handler)
handler = optls.NewPeerTLSMiddleware(handler)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
result := rr.Result()
defer result.Body.Close()
assert.Equal(t, 401, result.StatusCode)
}
func TestAuthMiddleware_HappyPath(t *testing.T) {
req, _ := http.NewRequest("GET", "/", nil)
req.TLS = &tls.ConnectionState{
PeerCertificates: []*x509.Certificate{{DNSNames: []string{"client.oplabs.co"}}},
}
handlerInvoked := false
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientInfo := ClientInfoFromContext(r.Context())
assert.Equal(t, "client.oplabs.co", clientInfo.ClientName)
handlerInvoked = true
})
handler = NewAuthMiddleware()(handler)
handler = optls.NewPeerTLSMiddleware(handler)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
result := rr.Result()
defer result.Body.Close()
assert.Equal(t, 200, result.StatusCode)
assert.True(t, handlerInvoked)
}

@ -0,0 +1,62 @@
package service
import (
"errors"
"fmt"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common/hexutil"
"gopkg.in/yaml.v3"
)
type AuthConfig struct {
ClientName string `yaml:"name"`
KeyName string `yaml:"key"`
ToAddresses []string `yaml:"toAddresses"`
MaxValue string `yaml:"maxValue"`
}
func (c AuthConfig) MaxValueToInt() *big.Int {
return hexutil.MustDecodeBig(c.MaxValue)
}
type SignerServiceConfig struct {
Auth []AuthConfig `yaml:"auth"`
}
func ReadConfig(path string) (SignerServiceConfig, error) {
config := SignerServiceConfig{}
data, err := os.ReadFile(path)
if err != nil {
return config, err
}
if err := yaml.Unmarshal(data, &config); err != nil {
return config, err
}
for _, authConfig := range config.Auth {
for _, toAddress := range authConfig.ToAddresses {
if _, err := hexutil.Decode(toAddress); err != nil {
return config, fmt.Errorf("invalid toAddress '%s' in auth config: %w", toAddress, err)
}
if authConfig.MaxValue != "" {
if _, err := hexutil.DecodeBig(authConfig.MaxValue); err != nil {
return config, fmt.Errorf("invalid maxValue '%s' in auth config: %w", toAddress, err)
}
}
}
}
return config, err
}
func (s SignerServiceConfig) GetAuthConfigForClient(clientName string) (*AuthConfig, error) {
if clientName == "" {
return nil, errors.New("client name is empty")
}
for _, ac := range s.Auth {
if ac.ClientName == clientName {
return &ac, nil
}
}
return nil, fmt.Errorf("client '%s' is not authorized to use any keys", clientName)
}

@ -0,0 +1,11 @@
package service
type InvalidTransactionError struct{ message string }
func (e *InvalidTransactionError) Error() string { return e.message }
func (e *InvalidTransactionError) ErrorCode() int { return -32010 }
type UnauthorizedTransactionError struct{ message string }
func (e *UnauthorizedTransactionError) Error() string { return e.message }
func (e *UnauthorizedTransactionError) ErrorCode() int { return -32011 }

@ -0,0 +1,12 @@
package service
import "github.com/prometheus/client_golang/prometheus"
var (
MetricSignTransactionTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "signer_signtransaction_total",
Help: ""},
[]string{"client", "status", "error"},
)
)

@ -0,0 +1,275 @@
//go:generate mockgen -destination=mock_kms.go -package=provider github.com/ethereum-optimism/infra/op-signer/service/provider CloudKMSClient
package provider
import (
"bytes"
"context"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"hash/crc32"
"math/big"
kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/log"
gax "github.com/googleapis/gax-go"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var (
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
oidNamedCurveSECP256K1 = asn1.ObjectIdentifier{1, 3, 132, 0, 10}
)
type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
type CloudKMSClient interface {
GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest, opts ...gax.CallOption) (*kmspb.PublicKey, error)
AsymmetricSign(context context.Context, req *kmspb.AsymmetricSignRequest, opts ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error)
}
type CloudKMSSignatureProvider struct {
logger log.Logger
client CloudKMSClient
}
func NewCloudKMSSignatureProvider(logger log.Logger) SignatureProvider {
ctx := context.Background()
client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
logger.Error("failed to initialize kms client", "error", err)
panic(err)
}
return &CloudKMSSignatureProvider{logger, client}
}
func NewCloudKMSSignatureProviderWithClient(logger log.Logger, client CloudKMSClient) SignatureProvider {
return &CloudKMSSignatureProvider{logger, client}
}
func crc32c(data []byte) uint32 {
t := crc32.MakeTable(crc32.Castagnoli)
return crc32.Checksum(data, t)
}
func createSignRequestFromDigest(keyName string, digest []byte) *kmspb.AsymmetricSignRequest {
digestCRC32C := crc32c(digest)
return &kmspb.AsymmetricSignRequest{
Name: keyName,
Digest: &kmspb.Digest{
Digest: &kmspb.Digest_Sha256{
Sha256: digest,
},
},
DigestCrc32C: wrapperspb.Int64(int64(digestCRC32C)),
}
}
// SignDigest signs the digest with a given Cloud KMS keyname and returns a compact recoverable signature.
// If the keyName provided is not a EC_SIGN_SECP256K1_SHA256 key, the result will be an error.
func (c *CloudKMSSignatureProvider) SignDigest(
ctx context.Context,
keyName string,
digest []byte,
) ([]byte, error) {
publicKey, err := c.GetPublicKey(ctx, keyName)
if err != nil {
return nil, fmt.Errorf("failed to get public key: %w", err)
}
request := createSignRequestFromDigest(keyName, digest)
result, err := c.client.AsymmetricSign(ctx, request)
if err != nil {
return nil, fmt.Errorf("cloud kms sign request failed: %w", err)
}
if result.Name != request.Name {
return nil, errors.New("cloud kms sign request corrupted in transit")
}
if !result.VerifiedDigestCrc32C {
return nil, errors.New("cloud kms sign request corrupted in transit")
}
if int64(crc32c(result.Signature)) != result.SignatureCrc32C.Value {
return nil, errors.New("cloud kms sign response corrupted in transit")
}
c.logger.Debug(fmt.Sprintf("der signature: %s", hexutil.Encode(result.Signature)))
return convertToCompactRecoverableSignature(result.Signature, digest, publicKey)
}
func convertToCompactRecoverableSignature(derSignature, digest, publicKey []byte) ([]byte, error) {
signature, err := convertToCompactSignature(derSignature)
if err != nil {
// should never happen
return nil, fmt.Errorf("failed to convert to compact signature: %w", err)
}
// NOTE: so far I haven't seen CloudKMS produce a malleable signature
// but if it does happen, this can be handled as a retryable error by the client
if err := compactSignatureMalleabilityCheck(signature); err != nil {
// should never happen
return nil, fmt.Errorf("signature failed malleability check: %w", err)
}
if !secp256k1.VerifySignature(publicKey, digest, signature) {
// should never happen
return nil, errors.New("signature could not be verified with public key")
}
recId, err := calculateRecoveryID(signature, digest, publicKey)
if err != nil {
// should never happen
return nil, fmt.Errorf("failed to calculate recovery id: %w", err)
}
signature = append(signature, byte(recId))
return signature, nil
}
// convertToCompactSignature compacts a DER signature output from kms (>70 bytes) into 64 bytes
func convertToCompactSignature(derSignature []byte) ([]byte, error) {
var parsedSig struct{ R, S *big.Int }
if _, err := asn1.Unmarshal(derSignature, &parsedSig); err != nil {
return nil, fmt.Errorf("asn1.Unmarshal error: %w", err)
}
curveOrderLen := 32
signature := make([]byte, 2*curveOrderLen)
// if S is non-canonical, lower it
curveOrder := secp256k1.S256().Params().Params().N
if parsedSig.S.Cmp(new(big.Int).Div(curveOrder, big.NewInt(2))) > 0 {
parsedSig.S = new(big.Int).Sub(curveOrder, parsedSig.S)
}
// left pad R and S with zeroes
rBytes := parsedSig.R.Bytes()
sBytes := parsedSig.S.Bytes()
copy(signature[curveOrderLen-len(rBytes):], rBytes)
copy(signature[len(signature)-len(sBytes):], sBytes)
return signature, nil
}
// calculateRecoveryID calculates the signature recovery id (65th byte, [0-3])
func calculateRecoveryID(signature, digest, pubKey []byte) (int, error) {
recId := -1
var errorRes error
for i := 0; i < 4; i++ {
recSig := append(signature, byte(i))
publicKey, err := secp256k1.RecoverPubkey(digest, recSig)
if err != nil {
errorRes = err
continue
}
if bytes.Equal(publicKey, pubKey) {
recId = i
break
}
}
if recId == -1 {
return recId, fmt.Errorf("failed to calculate recovery id, should never happen: %w", errorRes)
}
return recId, nil
}
// compactSignatureMalleabilityCheck checks if signature can be used to produce a new valid signature
// pulled from go-ethereum/crypto/secp256k1/secp256_test.go
// see: http://coders-errand.com/malleability-ecdsa-signatures/
func compactSignatureMalleabilityCheck(sig []byte) error {
b := int(sig[32])
if b < 0 {
return fmt.Errorf("highest bit is negative: %d", b)
}
if ((b >> 7) == 1) != ((b & 0x80) == 0x80) {
return fmt.Errorf("highest bit: %d bit >> 7: %d", b, b>>7)
}
if (b & 0x80) == 0x80 {
return fmt.Errorf("highest bit: %d bit & 0x80: %d", b, b&0x80)
}
return nil
}
// GetPublicKey returns a decoded secp256k1 public key.
func (c *CloudKMSSignatureProvider) GetPublicKey(
ctx context.Context,
keyName string,
) ([]byte, error) {
request := kmspb.GetPublicKeyRequest{
Name: keyName,
}
result, err := c.client.GetPublicKey(ctx, &request)
if err != nil {
return nil, fmt.Errorf("kms get public key request failed: %w", err)
}
key := []byte(result.Pem)
if int64(crc32c(key)) != result.PemCrc32C.Value {
return nil, errors.New("cloud kms public key response corrupted in transit")
}
return decodePublicKeyPEM(key)
}
// decodePublicKeyPEM decodes a PEM ECDSA public key with secp256k1 curve
func decodePublicKeyPEM(key []byte) ([]byte, error) {
block, rest := pem.Decode([]byte(key))
if len(rest) > 0 {
return nil, fmt.Errorf("crypto: failed to parse PEM string, not all bytes in PEM key were decoded: %x", rest)
}
pkBytes, err := x509ParseECDSAPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("crypto: failed to parse PEM string: %w", err)
}
return pkBytes, err
}
// x509ParseECDSAPublicKey parses a DER-encoded public key and ensures secp256k1 curve
func x509ParseECDSAPublicKey(derBytes []byte) ([]byte, error) {
var pki publicKeyInfo
if rest, err := asn1.Unmarshal(derBytes, &pki); err != nil {
return nil, err
} else if len(rest) != 0 {
return nil, errors.New("x509: trailing data after ASN.1 of public-key")
}
if !pki.Algorithm.Algorithm.Equal(oidPublicKeyECDSA) {
return nil, errors.New("x509: unknown public key algorithm")
}
asn1Data := pki.PublicKey.RightAlign()
paramsData := pki.Algorithm.Parameters.FullBytes
namedCurveOID := new(asn1.ObjectIdentifier)
rest, err := asn1.Unmarshal(paramsData, namedCurveOID)
if err != nil {
return nil, fmt.Errorf("x509: failed to parse ECDSA parameters as named curve: %w", err)
}
if len(rest) != 0 {
return nil, errors.New("x509: trailing data after ECDSA parameters")
}
if !namedCurveOID.Equal(oidNamedCurveSECP256K1) {
return nil, errors.New("x509: unsupported elliptic curve")
}
if asn1Data[0] != 4 { // uncompressed form
return nil, errors.New("x509: only uncompressed keys are supported")
}
return asn1Data, nil
}

@ -0,0 +1,194 @@
package provider
import (
"context"
"crypto/ecdsa"
"crypto/rand"
"io"
"testing"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cloud.google.com/go/kms/apiv1/kmspb"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/log"
)
const (
CloudKMSPemPublicKey = `-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEQpdToIk9lwjBdl0VcqXl7AwqhB9NwRf+
IHRNqIUNa8vAH/5l5MGXO/qVT5D/4sOTfpd29BQAkDVOgTAneA2Vrg==
-----END PUBLIC KEY-----`
)
func generateKey() *ecdsa.PrivateKey {
key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
if err != nil {
panic(err)
}
return key
}
// pulled from go-ethereum/crypto/secp256k1/secp256_test.go
func csprngEntropy(n int) []byte {
buf := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
return buf
}
func TestCloudKMS_SignDigest(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockClient := NewMockCloudKMSClient(ctrl)
provider := NewCloudKMSSignatureProviderWithClient(log.Root(), mockClient)
keyName := "keyName"
digest, _ := hexutil.Decode("0x8dabbae6d856bb7ab93bc35b74c1303975a3f70f942d033e8591a9f8c897ae42")
derSignature, _ := hexutil.Decode("0x30450221008680faa49fd6653d273fb34393a47efac44b8f4a4de62bbe11a65ee53739e9bb0220350897677c32d67dc1e520d7458c5cca4a7fe49a3e9d74bdef1ec96836148661")
pemPublicKey := []byte(CloudKMSPemPublicKey)
var tests = []struct {
testName string
keyName string
digest []byte
respError error
respVerifiedDigestCrc32C bool
respSignatureCrc32 uint32
wantErr bool
}{
{"happy path", keyName, digest, nil, true, crc32c(derSignature), false},
{"req failure", keyName, digest, assert.AnError, true, crc32c(derSignature), true},
{"invalid req keyName", "wrongKeyName", digest, nil, true, crc32c(derSignature), true},
{"invalid req crc32", keyName, digest, nil, false, crc32c(derSignature), true},
{"invalid resp crc32", keyName, digest, nil, true, crc32c([]byte("")), true},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
signRequest := createSignRequestFromDigest(tt.keyName, tt.digest)
mockClient.EXPECT().AsymmetricSign(gomock.Any(), signRequest).Return(
&kmspb.AsymmetricSignResponse{
Name: keyName,
Signature: derSignature,
VerifiedDigestCrc32C: tt.respVerifiedDigestCrc32C,
SignatureCrc32C: wrapperspb.Int64(int64(tt.respSignatureCrc32)),
},
tt.respError,
)
getPublicKeyRequest := &kmspb.GetPublicKeyRequest{Name: tt.keyName}
mockClient.EXPECT().GetPublicKey(gomock.Any(), getPublicKeyRequest).Return(
&kmspb.PublicKey{
Pem: string(pemPublicKey),
PemCrc32C: wrapperspb.Int64(int64(crc32c(pemPublicKey))),
},
nil,
)
publicKey, _ := decodePublicKeyPEM(pemPublicKey)
wantSignature, _ := convertToCompactRecoverableSignature(derSignature, tt.digest, publicKey)
signature, err := provider.SignDigest(context.TODO(), tt.keyName, tt.digest)
if !tt.wantErr {
assert.Nil(t, err)
assert.Equal(t, wantSignature, signature)
// make sure recoverable pubkey is as expected
recoveredPublicKey, err := crypto.Ecrecover(tt.digest, signature)
assert.Nil(t, err)
assert.Equal(t, publicKey, recoveredPublicKey)
} else {
assert.Error(t, err)
assert.Nil(t, signature)
}
})
}
}
func TestCloudKMS_GetPublicKey(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockClient := NewMockCloudKMSClient(ctrl)
provider := NewCloudKMSSignatureProviderWithClient(log.Root(), mockClient)
keyName := "keyName"
pemPublicKey := []byte(CloudKMSPemPublicKey)
wantPublicKey, _ := hexutil.Decode("0x04429753a0893d9708c1765d1572a5e5ec0c2a841f4dc117fe20744da8850d6bcbc01ffe65e4c1973bfa954f90ffe2c3937e9776f4140090354e813027780d95ae")
var tests = []struct {
testName string
keyName string
respError error
respPemCrc32 uint32
wantErr bool
}{
{"happy path", keyName, nil, crc32c(pemPublicKey), false},
{"req failure", keyName, assert.AnError, crc32c(pemPublicKey), false},
{"invalid resp crc32", keyName, assert.AnError, crc32c([]byte("")), true},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
request := &kmspb.GetPublicKeyRequest{Name: tt.keyName}
mockClient.EXPECT().GetPublicKey(gomock.Any(), request).Return(
&kmspb.PublicKey{
Pem: string(pemPublicKey),
PemCrc32C: wrapperspb.Int64(int64(tt.respPemCrc32)),
},
nil,
)
publicKey, err := provider.GetPublicKey(context.TODO(), tt.keyName)
if !tt.wantErr {
assert.Nil(t, err)
assert.Equal(t, wantPublicKey, publicKey)
} else {
assert.Error(t, err)
assert.Nil(t, publicKey)
}
})
}
}
// TestVerifySignatureFromRecoveredPublicKey tests that the compact signature can
// recover a publicKey that can be used to verify the original DER signature.
// Since all other reference implementations produce compact, recoverable signatures already,
// this serves as the test that converToCompactSignture and calculateRecoveryID produce expected results,
func TestVerifySignatureFromRecoveredPublicKey(t *testing.T) {
key := generateKey()
pubKey := crypto.FromECDSAPub(&key.PublicKey)
const TestCount = 1000
for i := 0; i < TestCount; i++ {
digest := csprngEntropy(32)
derSig, err := ecdsa.SignASN1(rand.Reader, key, digest)
if err != nil {
panic(err)
}
sig, err := convertToCompactSignature(derSig)
assert.Nil(t, err)
assert.Len(t, sig, 64)
assert.Nil(t, compactSignatureMalleabilityCheck(sig))
recId, err := calculateRecoveryID(sig, digest, pubKey)
assert.Nil(t, err)
assert.GreaterOrEqual(t, recId, 0)
assert.Less(t, recId, 4)
sig = append(sig, byte(recId))
recoveredRawPubKey, err := secp256k1.RecoverPubkey(digest, sig)
assert.Nil(t, err)
recoveredPubKey, err := crypto.UnmarshalPubkey(recoveredRawPubKey)
require.NoError(t, err)
assert.True(t, ecdsa.VerifyASN1(recoveredPubKey, digest, derSig))
}
}

@ -0,0 +1,77 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ethereum-optimism/infra/op-signer/service/provider (interfaces: CloudKMSClient)
// Package provider is a generated GoMock package.
package provider
import (
context "context"
reflect "reflect"
kmspb "cloud.google.com/go/kms/apiv1/kmspb"
gomock "github.com/golang/mock/gomock"
gax "github.com/googleapis/gax-go/v2"
)
// MockCloudKMSClient is a mock of CloudKMSClient interface.
type MockCloudKMSClient struct {
ctrl *gomock.Controller
recorder *MockCloudKMSClientMockRecorder
}
// MockCloudKMSClientMockRecorder is the mock recorder for MockCloudKMSClient.
type MockCloudKMSClientMockRecorder struct {
mock *MockCloudKMSClient
}
// NewMockCloudKMSClient creates a new mock instance.
func NewMockCloudKMSClient(ctrl *gomock.Controller) *MockCloudKMSClient {
mock := &MockCloudKMSClient{ctrl: ctrl}
mock.recorder = &MockCloudKMSClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCloudKMSClient) EXPECT() *MockCloudKMSClientMockRecorder {
return m.recorder
}
// AsymmetricSign mocks base method.
func (m *MockCloudKMSClient) AsymmetricSign(arg0 context.Context, arg1 *kmspb.AsymmetricSignRequest, arg2 ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AsymmetricSign", varargs...)
ret0, _ := ret[0].(*kmspb.AsymmetricSignResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AsymmetricSign indicates an expected call of AsymmetricSign.
func (mr *MockCloudKMSClientMockRecorder) AsymmetricSign(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AsymmetricSign", reflect.TypeOf((*MockCloudKMSClient)(nil).AsymmetricSign), varargs...)
}
// GetPublicKey mocks base method.
func (m *MockCloudKMSClient) GetPublicKey(arg0 context.Context, arg1 *kmspb.GetPublicKeyRequest, arg2 ...gax.CallOption) (*kmspb.PublicKey, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetPublicKey", varargs...)
ret0, _ := ret[0].(*kmspb.PublicKey)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPublicKey indicates an expected call of GetPublicKey.
func (mr *MockCloudKMSClientMockRecorder) GetPublicKey(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKey", reflect.TypeOf((*MockCloudKMSClient)(nil).GetPublicKey), varargs...)
}

@ -0,0 +1,65 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ethereum-optimism/infra/op-signer/service/provider (interfaces: SignatureProvider)
// Package provider is a generated GoMock package.
package provider
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockSignatureProvider is a mock of SignatureProvider interface.
type MockSignatureProvider struct {
ctrl *gomock.Controller
recorder *MockSignatureProviderMockRecorder
}
// MockSignatureProviderMockRecorder is the mock recorder for MockSignatureProvider.
type MockSignatureProviderMockRecorder struct {
mock *MockSignatureProvider
}
// NewMockSignatureProvider creates a new mock instance.
func NewMockSignatureProvider(ctrl *gomock.Controller) *MockSignatureProvider {
mock := &MockSignatureProvider{ctrl: ctrl}
mock.recorder = &MockSignatureProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSignatureProvider) EXPECT() *MockSignatureProviderMockRecorder {
return m.recorder
}
// GetPublicKey mocks base method.
func (m *MockSignatureProvider) GetPublicKey(arg0 context.Context, arg1 string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublicKey", arg0, arg1)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPublicKey indicates an expected call of GetPublicKey.
func (mr *MockSignatureProviderMockRecorder) GetPublicKey(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKey", reflect.TypeOf((*MockSignatureProvider)(nil).GetPublicKey), arg0, arg1)
}
// SignDigest mocks base method.
func (m *MockSignatureProvider) SignDigest(arg0 context.Context, arg1 string, arg2 []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignDigest", arg0, arg1, arg2)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SignDigest indicates an expected call of SignDigest.
func (mr *MockSignatureProviderMockRecorder) SignDigest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignDigest", reflect.TypeOf((*MockSignatureProvider)(nil).SignDigest), arg0, arg1, arg2)
}

@ -0,0 +1,9 @@
//go:generate mockgen -destination=mock_provider.go -package=provider github.com/ethereum-optimism/infra/op-signer/service/provider SignatureProvider
package provider
import "context"
type SignatureProvider interface {
SignDigest(ctx context.Context, keyName string, digest []byte) ([]byte, error)
GetPublicKey(ctx context.Context, keyName string) ([]byte, error)
}

@ -0,0 +1,148 @@
package service
import (
"context"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/prometheus/client_golang/prometheus"
"github.com/ethereum-optimism/infra/op-signer/service/provider"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/signer"
)
type SignerService struct {
logger log.Logger
config SignerServiceConfig
provider provider.SignatureProvider
}
func NewSignerService(logger log.Logger, config SignerServiceConfig) *SignerService {
return NewSignerServiceWithProvider(logger, config, provider.NewCloudKMSSignatureProvider(logger))
}
func NewSignerServiceWithProvider(
logger log.Logger,
config SignerServiceConfig,
provider provider.SignatureProvider,
) *SignerService {
return &SignerService{logger, config, provider}
}
func (s *SignerService) RegisterAPIs(server *oprpc.Server) {
server.AddAPI(rpc.API{
Namespace: "eth",
Service: s,
})
}
func containsNormalized(s []string, e string) bool {
for _, a := range s {
if strings.EqualFold(a, e) {
return true
}
}
return false
}
// SignTransaction will sign the given transaction with the key configured for the authenticated client
func (s *SignerService) SignTransaction(ctx context.Context, args signer.TransactionArgs) (hexutil.Bytes, error) {
clientInfo := ClientInfoFromContext(ctx)
authConfig, err := s.config.GetAuthConfigForClient(clientInfo.ClientName)
if err != nil {
return nil, rpc.HTTPError{StatusCode: 403, Status: "Forbidden", Body: []byte(err.Error())}
}
labels := prometheus.Labels{"client": clientInfo.ClientName, "status": "error", "error": ""}
defer func() {
MetricSignTransactionTotal.With(labels).Inc()
}()
if err := args.Check(); err != nil {
s.logger.Warn("invalid signing arguments", "err", err)
labels["error"] = "invalid_transaction"
return nil, &InvalidTransactionError{message: err.Error()}
}
if len(authConfig.ToAddresses) > 0 && !containsNormalized(authConfig.ToAddresses, args.To.Hex()) {
return nil, &UnauthorizedTransactionError{"to address not authorized"}
}
if len(authConfig.MaxValue) > 0 && args.Value.ToInt().Cmp(authConfig.MaxValueToInt()) > 0 {
return nil, &UnauthorizedTransactionError{"value exceeds maximum"}
}
txData, err := args.ToTransactionData()
if err != nil {
labels["error"] = "transaction_args_error"
return nil, &InvalidTransactionError{err.Error()}
}
tx := types.NewTx(txData)
txSigner := types.LatestSignerForChainID(tx.ChainId())
digest := txSigner.Hash(tx)
signature, err := s.provider.SignDigest(ctx, authConfig.KeyName, digest.Bytes())
if err != nil {
labels["error"] = "sign_error"
return nil, &InvalidTransactionError{err.Error()}
}
signed, err := tx.WithSignature(txSigner, signature)
if err != nil {
labels["error"] = "invalid_transaction_error"
return nil, &InvalidTransactionError{err.Error()}
}
signerFrom, err := txSigner.Sender(signed)
if err != nil {
labels["error"] = "sign_error"
return nil, &InvalidTransactionError{err.Error()}
}
// sanity check that we used the right account
if args.From != nil && *args.From != signerFrom {
s.logger.Warn("user is trying to sign with different account than actual signer-provider",
"provider", signerFrom, "request", *args.From)
labels["error"] = "sign_error"
return nil, &InvalidTransactionError{"unexpected from address"}
}
txraw, err := signed.MarshalBinary()
if err != nil {
labels["error"] = "transaction_marshal_error"
return nil, &InvalidTransactionError{err.Error()}
}
labels["status"] = "success"
txTo := ""
if tx.To() != nil {
txTo = tx.To().Hex()
}
s.logger.Info(
"Signed transaction",
"digest", hexutil.Encode(digest.Bytes()),
"client.name", clientInfo.ClientName,
"client.keyname", authConfig.KeyName,
"tx.type", tx.Type(),
"tx.raw", hexutil.Encode(txraw),
"tx.value", tx.Value(),
"tx.to", txTo,
"tx.nonce", tx.Nonce(),
"tx.gas", tx.Gas(),
"tx.gasprice", tx.GasPrice(),
"tx.gastipcap", tx.GasTipCap(),
"tx.gasfeecap", tx.GasFeeCap(),
"tx.type", tx.Type(),
"tx.hash", tx.Hash().Hex(),
"tx.chainid", tx.ChainId(),
"tx.blobhashes", tx.BlobHashes(),
"tx.blobfeecap", tx.BlobGasFeeCap(),
"signature", hexutil.Encode(signature),
)
return hexutil.Bytes(txraw), nil
}

@ -0,0 +1,182 @@
package service
import (
"context"
"errors"
"math/big"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/infra/op-signer/service/provider"
clientSigner "github.com/ethereum-optimism/optimism/op-service/signer"
)
func createEIP1559Tx() *types.Transaction {
aa := common.HexToAddress("0x000000000000000000000000000000000000aaaa")
accesses := types.AccessList{types.AccessTuple{
Address: aa,
StorageKeys: []common.Hash{{0}},
}}
txdata := &types.DynamicFeeTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
To: &aa,
Gas: 30000,
GasFeeCap: big.NewInt(1),
GasTipCap: big.NewInt(1),
AccessList: accesses,
Data: []byte{},
Value: big.NewInt(1),
}
tx := types.NewTx(txdata)
return tx
}
func createBlobTx() *types.Transaction {
aa := common.HexToAddress("0x000000000000000000000000000000000000aaaa")
accesses := types.AccessList{types.AccessTuple{
Address: aa,
StorageKeys: []common.Hash{{0}},
}}
txdata := &types.BlobTx{
ChainID: uint256.MustFromBig(params.AllEthashProtocolChanges.ChainID),
Nonce: 0,
To: aa,
Gas: 30000,
GasFeeCap: uint256.NewInt(1),
GasTipCap: uint256.NewInt(1),
AccessList: accesses,
Data: []byte{},
Value: uint256.NewInt(1),
BlobFeeCap: uint256.NewInt(1),
BlobHashes: []common.Hash{common.HexToHash("c0ffee")},
}
tx := types.NewTx(txdata)
return tx
}
var config = SignerServiceConfig{
Auth: []AuthConfig{
{ClientName: "client.oplabs.co", KeyName: "keyName"},
{ClientName: "alt-client.oplabs.co", KeyName: "altKeyName"},
{ClientName: "authorized-to.oplabs.co", KeyName: "keyName", ToAddresses: []string{"0x000000000000000000000000000000000000Aaaa"}},
{ClientName: "unauthorized-to.oplabs.co", KeyName: "keyName", ToAddresses: []string{"0x000000000000000000000000000000000000bbbb"}},
{ClientName: "within-max-value.oplabs.co", KeyName: "keyName", MaxValue: hexutil.EncodeBig(big.NewInt(2))},
{ClientName: "exceeds-max-value.oplabs.co", KeyName: "keyName", MaxValue: hexutil.EncodeBig(big.NewInt(0))},
},
}
type testCase struct {
name string
template func() *types.Transaction
}
var testTxs = []testCase{
{"regular", createEIP1559Tx},
{"blob-tx", createBlobTx},
}
func TestSignTransaction(t *testing.T) {
for _, tc := range testTxs {
tc := tc
t.Run(tc.name, func(t *testing.T) {
testSignTransaction(t, tc.template())
})
}
}
func testSignTransaction(t *testing.T, tx *types.Transaction) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
signer := types.LatestSignerForChainID(tx.ChainId())
digest := signer.Hash(tx).Bytes()
priv, err := crypto.GenerateKey()
require.NoError(t, err)
sender := crypto.PubkeyToAddress(priv.PublicKey)
signature, err := crypto.Sign(digest, priv)
require.NoError(t, err)
args := clientSigner.NewTransactionArgsFromTransaction(tx.ChainId(), nil, tx)
missingNonce := clientSigner.NewTransactionArgsFromTransaction(tx.ChainId(), nil, tx)
missingNonce.Nonce = nil
validFrom := clientSigner.NewTransactionArgsFromTransaction(tx.ChainId(), nil, tx)
validFrom.From = &sender
invalidFrom := clientSigner.NewTransactionArgsFromTransaction(tx.ChainId(), nil, tx)
random := common.HexToAddress("1234")
invalidFrom.From = &random
// signature, _ := hexutil.Decode("0x5392c93b50eb9e3412ab43d378048d4f7d644f3cea02acb529f07e2babba1d3a332377f4abe24a40030b3ff6bff3413a44364aad4665f4e24117466328ce8d3600")
tests := []struct {
testName string
args clientSigner.TransactionArgs
digest []byte
clientName string
wantKeyName string
wantErrCode int
}{
{"happy path", *args, digest, "client.oplabs.co", "keyName", 0},
{"nonce not specified", *missingNonce, digest, "client.oplabs.co", "keyName", -32010},
{"happy path - different client and key", *args, digest, "alt-client.oplabs.co", "altKeyName", 0},
{"client not authorized", *args, digest, "forbidden-client.oplabs.co", "keyName", 403},
{"client empty", *args, digest, "", "", 403},
{"authorized to address", *args, digest, "authorized-to.oplabs.co", "keyName", 0},
{"unauthorized to address", *args, digest, "unauthorized-to.oplabs.co", "keyName", -32011},
{"within max value", *args, digest, "within-max-value.oplabs.co", "keyName", 0},
{"exceeds max value", *args, digest, "exceeds-max-value.oplabs.co", "keyName", -32011},
{"valid from", *validFrom, digest, "client.oplabs.co", "keyName", 0},
{"invalid from", *invalidFrom, digest, "client.oplabs.co", "keyName", -32010},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
mockSignatureProvider := provider.NewMockSignatureProvider(ctrl)
service := NewSignerServiceWithProvider(log.Root(), config, mockSignatureProvider)
ctx := context.WithValue(context.TODO(), clientInfoContextKey{}, ClientInfo{ClientName: tt.clientName})
if tt.wantErrCode == 0 || tt.testName == "invalid from" {
mockSignatureProvider.EXPECT().
SignDigest(ctx, tt.wantKeyName, tt.digest).
Return(signature, nil)
}
resp, err := service.SignTransaction(ctx, tt.args)
if tt.wantErrCode == 0 {
assert.Nil(t, err)
if assert.NotNil(t, resp) {
assert.NotEmpty(t, resp)
}
} else {
assert.NotNil(t, err)
assert.Nil(t, resp)
var rpcErr rpc.Error
var httpErr rpc.HTTPError
if errors.As(err, &rpcErr) {
assert.Equal(t, tt.wantErrCode, rpcErr.ErrorCode())
} else if errors.As(err, &httpErr) {
assert.Equal(t, tt.wantErrCode, httpErr.StatusCode)
} else {
assert.Fail(t, "returned error is not an rpc.Error or rpc.HTTPError")
}
}
})
}
}

53
op-signer/test-rpc.json Normal file

@ -0,0 +1,53 @@
[
{
"id": "1",
"jsonrpc": "2.0",
"method": "eth_signTransaction",
"params": [
{
"to": "0x000000000000000000000000000000000000aaaa",
"gas": "0x7530",
"gasPrice": null,
"maxFeePerGas": "0x1",
"maxPriorityFeePerGas": "0x1",
"value": "0x1",
"nonce": "0x0",
"data": "0x",
"accessList": [
{
"address": "0x000000000000000000000000000000000000aaaa",
"storageKeys": [
"0x0000000000000000000000000000000000000000000000000000000000000000"
]
}
],
"chainId": "0x539"
}
]
},
{
"id": "1",
"jsonrpc": "2.0",
"method": "eth_signTransaction",
"params": [
{
"to": "0x000000000000000000000000000000000000aaaa",
"gas": "0x7530",
"gasPrice": null,
"maxFeePerGas": "0x1",
"maxPriorityFeePerGas": "0x1",
"value": "0x1",
"data": "0x",
"accessList": [
{
"address": "0x000000000000000000000000000000000000aaaa",
"storageKeys": [
"0x0000000000000000000000000000000000000000000000000000000000000000"
]
}
],
"chainId": "0x539"
}
]
}
]

@ -11,6 +11,7 @@ import semver
# Minimum version numbers for packages migrating from legacy versioning.
MIN_VERSIONS = {
'proxyd': '4.6.1',
'op-signer': '0.0.1',
}
VALID_BUMPS = ('major', 'minor', 'patch', 'prerelease', 'finalize-prerelease')

@ -6,6 +6,7 @@ import semver
SERVICES = [
'proxyd',
'op-ufm',
'op-signer',
'op-conductor-mon',
]
VERSION_PATTERN = '^{service}/v\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$'