diff --git a/Dockerfile b/Dockerfile index bcf872e..4a3f228 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ FROM alpine:latest COPY --from=builder /app/mev-commit-oracle /usr/local/bin/mev-commit-oracle COPY --from=builder /app/key /key +COPY --from=builder /app/keystore /keystore COPY --from=builder /app/config.yaml /config.yaml COPY --from=builder /app/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/cmd/main.go b/cmd/main.go index fe2c8c5..11d5727 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,8 +7,10 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/primevprotocol/mev-oracle/pkg/keysigner" "github.com/primevprotocol/mev-oracle/pkg/node" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -53,6 +55,8 @@ func main() { type config struct { PrivKeyFile string `yaml:"priv_key_file" json:"priv_key_file"` + KeystorePath string `yaml:"keystore_path" json:"keystore_path"` + KeystorePassword string `yaml:"keystore_password" json:"keystore_password"` HTTPPort int `yaml:"http_port" json:"http_port"` LogLevel string `yaml:"log_level" json:"log_level"` L1RPCUrl string `yaml:"l1_rpc_url" json:"l1_rpc_url"` @@ -69,8 +73,8 @@ type config struct { } func checkConfig(cfg *config) error { - if cfg.PrivKeyFile == "" { - return fmt.Errorf("priv_key_file is required") + if cfg.PrivKeyFile == "" && (cfg.KeystorePath == "" || cfg.KeystorePassword == "") { + return fmt.Errorf("priv_key_file or keystore_path and keystore_password are required") } if cfg.HTTPPort == 0 { @@ -131,25 +135,15 @@ func start(c *cli.Context) error { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Logger = log.Output(os.Stdout).With().Caller().Logger() - privKeyFile := cfg.PrivKeyFile - if strings.HasPrefix(privKeyFile, "~/") { - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to get user home directory: %w", err) - } - - privKeyFile = filepath.Join(homeDir, privKeyFile[2:]) - } - - privKey, err := crypto.LoadECDSA(privKeyFile) + keySigner, err := setupKeySigner(&cfg) if err != nil { - return fmt.Errorf("failed to load private key from file '%s': %w", cfg.PrivKeyFile, err) + return fmt.Errorf("failed to setup key signer: %w", err) } common.HexToAddress(cfg.OracleContractAddr) nd, err := node.NewNode(&node.Options{ - PrivateKey: privKey, + KeySigner: keySigner, HTTPPort: cfg.HTTPPort, L1RPCUrl: cfg.L1RPCUrl, SettlementRPCUrl: cfg.SettlementRPCUrl, @@ -188,3 +182,40 @@ func start(c *cli.Context) error { return nil } + +func setupKeySigner(cfg *config) (keysigner.KeySigner, error) { + if cfg.PrivKeyFile == "" { + return setupKeystoreSigner(cfg) + } + return setupPrivateKeySigner(cfg) +} + +func setupKeystoreSigner(cfg *config) (keysigner.KeySigner, error) { + // Load the keystore file + ks := keystore.NewKeyStore(cfg.KeystorePath, keystore.LightScryptN, keystore.LightScryptP) + accounts := ks.Accounts() + if len(accounts) == 0 { + return nil, fmt.Errorf("no accounts found in keystore, path: %s", cfg.KeystorePath) + } + + account := accounts[0] + return keysigner.NewKeystoreSigner(ks, cfg.KeystorePassword, account), nil +} + +func setupPrivateKeySigner(cfg *config) (keysigner.KeySigner, error) { + privKeyFile := cfg.PrivKeyFile + if strings.HasPrefix(privKeyFile, "~/") { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get user home directory: %w", err) + } + + privKeyFile = filepath.Join(homeDir, privKeyFile[2:]) + } + + privKey, err := crypto.LoadECDSA(privKeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load private key from file '%s': %w", cfg.PrivKeyFile, err) + } + return keysigner.NewPrivateKeySigner(privKey), nil +} diff --git a/config.yaml b/config.yaml index 33508a4..9191e05 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,6 @@ priv_key_file: /key +keystore_path: /keystore +keystore_password: primev log_level: debug l1_rpc_url: settlement_rpc_url: http://sl-bootnode:8545 diff --git a/integrationtest/Dockerfile b/integrationtest/Dockerfile index 4392a15..4f81c5a 100644 --- a/integrationtest/Dockerfile +++ b/integrationtest/Dockerfile @@ -9,6 +9,7 @@ FROM alpine:latest COPY --from=builder /app/mev-commit-oracle /usr/local/bin/mev-commit-oracle COPY --from=builder /app/integrationtest/key /key +COPY --from=builder /app/keystore /keystore COPY --from=builder /app/integrationtest/config.yaml /config.yaml COPY --from=builder /app/integrationtest/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/integrationtest/config.yaml b/integrationtest/config.yaml index 96cf2f6..572d263 100644 --- a/integrationtest/config.yaml +++ b/integrationtest/config.yaml @@ -1,4 +1,6 @@ priv_key_file: /key +keystore_path: /keystore +keystore_password: primev log_level: debug l1_rpc_url: settlement_rpc_url: http://sl-bootnode:8545 diff --git a/integrationtest/keystore/UTC--2024-02-03T12-33-51.739458000Z--f39fd6e51aad88f6f4ce6ab8827279cfffb92266 b/integrationtest/keystore/UTC--2024-02-03T12-33-51.739458000Z--f39fd6e51aad88f6f4ce6ab8827279cfffb92266 new file mode 100644 index 0000000..9ca49c3 --- /dev/null +++ b/integrationtest/keystore/UTC--2024-02-03T12-33-51.739458000Z--f39fd6e51aad88f6f4ce6ab8827279cfffb92266 @@ -0,0 +1 @@ +{"address":"f39fd6e51aad88f6f4ce6ab8827279cfffb92266","crypto":{"cipher":"aes-128-ctr","ciphertext":"76c45c365f221cc9974ac51045cb947d053ace020d9d1343ddc8100b6d5ad5e4","cipherparams":{"iv":"751c74fd7dbce2e9c17cddff2ed36724"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"852baf4e7508c9da75956846da3b5a47e4f978f9068adb91c4e4ef18ce7b30ba"},"mac":"dce583625ec4e9821153bfec9d923752bc8d5161f7622c01afc227d537cceff5"},"id":"589bc600-ce98-4051-9d97-6aba0d6fc2fb","version":3} \ No newline at end of file diff --git a/keystore/UTC--2024-02-03T12-33-51.739458000Z--f39fd6e51aad88f6f4ce6ab8827279cfffb92266 b/keystore/UTC--2024-02-03T12-33-51.739458000Z--f39fd6e51aad88f6f4ce6ab8827279cfffb92266 new file mode 100644 index 0000000..9ca49c3 --- /dev/null +++ b/keystore/UTC--2024-02-03T12-33-51.739458000Z--f39fd6e51aad88f6f4ce6ab8827279cfffb92266 @@ -0,0 +1 @@ +{"address":"f39fd6e51aad88f6f4ce6ab8827279cfffb92266","crypto":{"cipher":"aes-128-ctr","ciphertext":"76c45c365f221cc9974ac51045cb947d053ace020d9d1343ddc8100b6d5ad5e4","cipherparams":{"iv":"751c74fd7dbce2e9c17cddff2ed36724"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"852baf4e7508c9da75956846da3b5a47e4f978f9068adb91c4e4ef18ce7b30ba"},"mac":"dce583625ec4e9821153bfec9d923752bc8d5161f7622c01afc227d537cceff5"},"id":"589bc600-ce98-4051-9d97-6aba0d6fc2fb","version":3} \ No newline at end of file diff --git a/pkg/keysigner/keysigner.go b/pkg/keysigner/keysigner.go new file mode 100644 index 0000000..3b9fd7e --- /dev/null +++ b/pkg/keysigner/keysigner.go @@ -0,0 +1,61 @@ +package keysigner + +import ( + "crypto/ecdsa" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type KeySigner interface { + GetAddress() common.Address + GetAuth(chainID *big.Int) (*bind.TransactOpts, error) +} + +type privateKeySigner struct { + privKey *ecdsa.PrivateKey +} + +func NewPrivateKeySigner(privKey *ecdsa.PrivateKey) *privateKeySigner { + return &privateKeySigner{ + privKey: privKey, + } +} + +func (pks *privateKeySigner) GetAddress() common.Address { + return crypto.PubkeyToAddress(pks.privKey.PublicKey) +} + +func (pks *privateKeySigner) GetAuth(chainID *big.Int) (*bind.TransactOpts, error) { + return bind.NewKeyedTransactorWithChainID(pks.privKey, chainID) +} + +type keystoreSigner struct { + keystore *keystore.KeyStore + password string + account accounts.Account +} + +func NewKeystoreSigner(keystore *keystore.KeyStore, password string, account accounts.Account) *keystoreSigner { + return &keystoreSigner{ + keystore: keystore, + password: password, + account: account, + } +} + +func (kss *keystoreSigner) GetAddress() common.Address { + return kss.account.Address +} + +func (kss *keystoreSigner) GetAuth(chainID *big.Int) (*bind.TransactOpts, error) { + if err := kss.keystore.Unlock(kss.account, kss.password); err != nil { + return nil, err + } + + return bind.NewKeyStoreTransactorWithChainID(kss.keystore, kss.account, chainID) +} diff --git a/pkg/node/node.go b/pkg/node/node.go index deba334..7a74c7a 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -18,6 +18,7 @@ import ( rollupclient "github.com/primevprotocol/contracts-abi/clients/Oracle" preconf "github.com/primevprotocol/contracts-abi/clients/PreConfCommitmentStore" "github.com/primevprotocol/mev-oracle/pkg/apiserver" + "github.com/primevprotocol/mev-oracle/pkg/keysigner" "github.com/primevprotocol/mev-oracle/pkg/l1Listener" "github.com/primevprotocol/mev-oracle/pkg/settler" "github.com/primevprotocol/mev-oracle/pkg/store" @@ -27,7 +28,7 @@ import ( ) type Options struct { - PrivateKey *ecdsa.PrivateKey + KeySigner keysigner.KeySigner HTTPPort int SettlementRPCUrl string L1RPCUrl string @@ -63,7 +64,7 @@ func NewNode(opts *Options) (*Node, error) { return nil, err } - owner := getEthAddressFromPubKey(opts.PrivateKey.Public().(*ecdsa.PublicKey)) + owner := opts.KeySigner.GetAddress() settlementClient, err := ethclient.Dial(opts.SettlementRPCUrl) if err != nil { @@ -114,7 +115,7 @@ func NewNode(opts *Options) (*Node, error) { for _, winner := range opts.OverrideWinners { err := setBuilderMapping( ctx, - opts.PrivateKey, + opts.KeySigner, chainID, settlementClient, oracleContract, @@ -148,7 +149,7 @@ func NewNode(opts *Options) (*Node, error) { updtrClosed := updtr.Start(ctx) settlr := settler.NewSettler( - opts.PrivateKey, + opts.KeySigner, chainID, owner, oracleContract, @@ -276,14 +277,14 @@ func (w *winnerOverrideL1Client) HeaderByNumber(ctx context.Context, number *big func setBuilderMapping( ctx context.Context, - privateKey *ecdsa.PrivateKey, + keySigner keysigner.KeySigner, chainID *big.Int, client *ethclient.Client, rc *rollupclient.Oracle, builderName string, builderAddress string, ) error { - auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) + auth, err := keySigner.GetAuth(chainID) if err != nil { return err } diff --git a/pkg/settler/settler.go b/pkg/settler/settler.go index dcda472..cbe440c 100644 --- a/pkg/settler/settler.go +++ b/pkg/settler/settler.go @@ -2,7 +2,6 @@ package settler import ( "context" - "crypto/ecdsa" "errors" "math/big" "time" @@ -10,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/primevprotocol/mev-oracle/pkg/keysigner" "github.com/prometheus/client_golang/prometheus" "github.com/rs/zerolog/log" ) @@ -51,7 +51,7 @@ type Transactor interface { } type Settler struct { - privateKey *ecdsa.PrivateKey + keySigner keysigner.KeySigner chainID *big.Int owner common.Address rollupClient Oracle @@ -61,7 +61,7 @@ type Settler struct { } func NewSettler( - privateKey *ecdsa.PrivateKey, + keySigner keysigner.KeySigner, chainID *big.Int, owner common.Address, rollupClient Oracle, @@ -73,14 +73,14 @@ func NewSettler( settlerRegister: settlerRegister, owner: owner, client: client, - privateKey: privateKey, + keySigner: keySigner, chainID: chainID, metrics: newMetrics(), } } func (s *Settler) getTransactOpts(ctx context.Context) (*bind.TransactOpts, error) { - auth, err := bind.NewKeyedTransactorWithChainID(s.privateKey, s.chainID) + auth, err := s.keySigner.GetAuth(s.chainID) if err != nil { return nil, err }