diff --git a/.circleci/config.yml b/.circleci/config.yml index 669b8910..158e9466 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,7 @@ workflows: # 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 diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 92822c07..0d39d44e 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -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: <>,<> 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: <>,<> + 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: <> + 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: <> + 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: diff --git a/.github/workflows/tag-service.yml b/.github/workflows/tag-service.yml index 60baa6fa..bc3dc983 100644 --- a/.github/workflows/tag-service.yml +++ b/.github/workflows/tag-service.yml @@ -18,6 +18,7 @@ on: required: true type: choice options: + - op-signer - op-ufm - proxyd prerelease: diff --git a/README.md b/README.md index 6e6cbed9..317c2c55 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/op-signer/.air.toml b/op-signer/.air.toml new file mode 100644 index 00000000..c19f6990 --- /dev/null +++ b/op-signer/.air.toml @@ -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 diff --git a/op-signer/.envrc b/op-signer/.envrc new file mode 100644 index 00000000..5488afb2 --- /dev/null +++ b/op-signer/.envrc @@ -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 diff --git a/op-signer/.gitignore b/op-signer/.gitignore new file mode 100644 index 00000000..d58875c5 --- /dev/null +++ b/op-signer/.gitignore @@ -0,0 +1,2 @@ +bin/ +tls/ diff --git a/op-signer/CHANGELOG.md b/op-signer/CHANGELOG.md new file mode 100644 index 00000000..4c1de158 --- /dev/null +++ b/op-signer/CHANGELOG.md @@ -0,0 +1 @@ +# @eth-optimism/signer diff --git a/op-signer/Dockerfile b/op-signer/Dockerfile new file mode 100644 index 00000000..090546cf --- /dev/null +++ b/op-signer/Dockerfile @@ -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"] diff --git a/op-signer/Makefile b/op-signer/Makefile new file mode 100644 index 00000000..c9cacad9 --- /dev/null +++ b/op-signer/Makefile @@ -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 diff --git a/op-signer/README.md b/op-signer/README.md new file mode 100644 index 00000000..d2f68145 --- /dev/null +++ b/op-signer/README.md @@ -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 +``` diff --git a/op-signer/app.go b/op-signer/app.go new file mode 100644 index 00000000..66445fea --- /dev/null +++ b/op-signer/app.go @@ -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 + } +} diff --git a/op-signer/client/client.go b/op-signer/client/client.go new file mode 100644 index 00000000..11612456 --- /dev/null +++ b/op-signer/client/client.go @@ -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 +} diff --git a/op-signer/cmd/main.go b/op-signer/cmd/main.go new file mode 100644 index 00000000..14362968 --- /dev/null +++ b/op-signer/cmd/main.go @@ -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) + } +} diff --git a/op-signer/config.go b/op-signer/config.go new file mode 100644 index 00000000..59a4045a --- /dev/null +++ b/op-signer/config.go @@ -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), + } +} diff --git a/op-signer/config.yaml b/op-signer/config.yaml new file mode 100644 index 00000000..5f1836b8 --- /dev/null +++ b/op-signer/config.yaml @@ -0,0 +1,3 @@ +auth: + - name: localhost + key: projects/my-gcp-project/locations/my-region/keyRings/my-ring/cryptoKeys/my-key/cryptoKeyVersions/1 diff --git a/op-signer/gen-local-tls.sh b/op-signer/gen-local-tls.sh new file mode 100755 index 00000000..ad505e30 --- /dev/null +++ b/op-signer/gen-local-tls.sh @@ -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') diff --git a/op-signer/go.mod b/op-signer/go.mod new file mode 100644 index 00000000..a138431e --- /dev/null +++ b/op-signer/go.mod @@ -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 diff --git a/op-signer/go.sum b/op-signer/go.sum new file mode 100644 index 00000000..2da1cdd5 --- /dev/null +++ b/op-signer/go.sum @@ -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= diff --git a/op-signer/service/auth.go b/op-signer/service/auth.go new file mode 100644 index 00000000..ffba405c --- /dev/null +++ b/op-signer/service/auth.go @@ -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 +} diff --git a/op-signer/service/auth_test.go b/op-signer/service/auth_test.go new file mode 100644 index 00000000..46d3e62c --- /dev/null +++ b/op-signer/service/auth_test.go @@ -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) +} diff --git a/op-signer/service/config.go b/op-signer/service/config.go new file mode 100644 index 00000000..b376343b --- /dev/null +++ b/op-signer/service/config.go @@ -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) +} diff --git a/op-signer/service/errors.go b/op-signer/service/errors.go new file mode 100644 index 00000000..1f76e472 --- /dev/null +++ b/op-signer/service/errors.go @@ -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 } diff --git a/op-signer/service/metrics.go b/op-signer/service/metrics.go new file mode 100644 index 00000000..c19e9797 --- /dev/null +++ b/op-signer/service/metrics.go @@ -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"}, + ) +) diff --git a/op-signer/service/provider/cloudkms.go b/op-signer/service/provider/cloudkms.go new file mode 100644 index 00000000..7b108ac0 --- /dev/null +++ b/op-signer/service/provider/cloudkms.go @@ -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 +} diff --git a/op-signer/service/provider/cloudkms_test.go b/op-signer/service/provider/cloudkms_test.go new file mode 100644 index 00000000..73f15e8d --- /dev/null +++ b/op-signer/service/provider/cloudkms_test.go @@ -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)) + } +} diff --git a/op-signer/service/provider/mock_kms.go b/op-signer/service/provider/mock_kms.go new file mode 100644 index 00000000..825a906a --- /dev/null +++ b/op-signer/service/provider/mock_kms.go @@ -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...) +} diff --git a/op-signer/service/provider/mock_provider.go b/op-signer/service/provider/mock_provider.go new file mode 100644 index 00000000..f873e6d8 --- /dev/null +++ b/op-signer/service/provider/mock_provider.go @@ -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) +} diff --git a/op-signer/service/provider/provider.go b/op-signer/service/provider/provider.go new file mode 100644 index 00000000..33158579 --- /dev/null +++ b/op-signer/service/provider/provider.go @@ -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) +} diff --git a/op-signer/service/service.go b/op-signer/service/service.go new file mode 100644 index 00000000..dcd1034e --- /dev/null +++ b/op-signer/service/service.go @@ -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 +} diff --git a/op-signer/service/service_test.go b/op-signer/service/service_test.go new file mode 100644 index 00000000..a2d8ae3b --- /dev/null +++ b/op-signer/service/service_test.go @@ -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") + } + } + }) + } +} diff --git a/op-signer/test-rpc.json b/op-signer/test-rpc.json new file mode 100644 index 00000000..1a20de1c --- /dev/null +++ b/op-signer/test-rpc.json @@ -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" + } + ] + } +] diff --git a/ops/tag-service/tag-service.py b/ops/tag-service/tag-service.py index c911043c..705cdc0c 100755 --- a/ops/tag-service/tag-service.py +++ b/ops/tag-service/tag-service.py @@ -11,6 +11,7 @@ # 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') diff --git a/ops/tag-service/tag-tool.py b/ops/tag-service/tag-tool.py index 19fbf7da..fac8f2f5 100644 --- a/ops/tag-service/tag-tool.py +++ b/ops/tag-service/tag-tool.py @@ -6,6 +6,7 @@ SERVICES = [ 'proxyd', 'op-ufm', + 'op-signer', 'op-conductor-mon', ] VERSION_PATTERN = '^{service}/v\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$'