Skip to content

Commit

Permalink
Merge pull request #29 from primevprotocol/feature/private-key-manage…
Browse files Browse the repository at this point in the history
…ment

implemented keystore support
  • Loading branch information
Mikelle authored Feb 8, 2024
2 parents eb5a84e + bd29360 commit 13fa307
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 37 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o mev-commit-oracle ./cmd/main.go
FROM alpine:latest

COPY --from=builder /app/mev-commit-oracle /usr/local/bin/mev-commit-oracle
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
Expand Down
104 changes: 91 additions & 13 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
contracts "github.com/primevprotocol/contracts-abi/config"
"github.com/primevprotocol/mev-oracle/pkg/keysigner"
"github.com/primevprotocol/mev-oracle/pkg/node"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
Expand All @@ -23,6 +26,7 @@ const (
defaultHTTPPort = 8080
defaultConfigDir = "~/.mev-commit-oracle"
defaultKeyFile = "key"
defaultKeystore = "keystore"
)

var (
Expand Down Expand Up @@ -138,6 +142,19 @@ var (
Usage: "Override winners for testing",
EnvVars: []string{"MEV_ORACLE_OVERRIDE_WINNERS"},
})

optionKeystorePassword = altsrc.NewStringFlag(&cli.StringFlag{
Name: "keystore-password",
Usage: "use to access keystore",
EnvVars: []string{"MEV_COMMIT_KEYSTORE_PASSWORD"},
})

optionKeystorePath = altsrc.NewStringFlag(&cli.StringFlag{
Name: "keystore-path",
Usage: "path to keystore location",
EnvVars: []string{"MEV_COMMIT_KEYSTORE_PATH"},
Value: filepath.Join(defaultConfigDir, defaultKeystore),
})
)

func main() {
Expand All @@ -157,6 +174,8 @@ func main() {
optionPgDbname,
optionLaggerdMode,
optionOverrideWinners,
optionKeystorePath,
optionKeystorePassword,
}
app := &cli.App{
Name: "mev-oracle",
Expand All @@ -168,7 +187,7 @@ func main() {
Flags: flags,
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc(optionConfig.Name)),
Action: func(c *cli.Context) error {
return start(c)
return initializeApplication(c)
},
},
}}
Expand Down Expand Up @@ -244,14 +263,30 @@ func getEthAddressFromPubKey(key *ecdsa.PublicKey) common.Address {
return common.BytesToAddress(address)
}

func start(c *cli.Context) error {
privKeyFile, err := resolveFilePath(c.String(optionPrivKeyFile.Name))
if err != nil {
return fmt.Errorf("failed to get private key file path: %w", err)
func initializeApplication(c *cli.Context) error {
if err := verifyKeystorePasswordPresence(c); err != nil {
return err
}
if err := launchOracleWithConfig(c); err != nil {
return err
}
return nil
}

if err := createKeyIfNotExists(c, privKeyFile); err != nil {
return fmt.Errorf("failed to create private key: %w", err)
// verifyKeystorePasswordPresence checks for the presence of a keystore password.
// it returns error, if keystore path is set and keystore password is not
func verifyKeystorePasswordPresence(c *cli.Context) error {
if c.IsSet(optionKeystorePath.Name) && !c.IsSet(optionKeystorePassword.Name) {
return cli.Exit("Password for encrypted keystore is missing", 1)
}
return nil
}

// launchOracleWithConfig configures and starts the oracle based on the CLI context or config.yaml file.
func launchOracleWithConfig(c *cli.Context) error {
keySigner, err := setupKeySigner(c)
if err != nil {
return fmt.Errorf("failed to setup key signer: %w", err)
}

lvl, _ := zerolog.ParseLevel(c.String(optionLogLevel.Name))
Expand All @@ -260,13 +295,8 @@ func start(c *cli.Context) error {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(os.Stdout).With().Caller().Logger()

privKey, err := crypto.LoadECDSA(privKeyFile)
if err != nil {
return fmt.Errorf("failed to load private key from file '%s': %w", privKeyFile, err)
}

nd, err := node.NewNode(&node.Options{
PrivateKey: privKey,
KeySigner: keySigner,
HTTPPort: c.Int(optionHTTPPort.Name),
L1RPCUrl: c.String(optionL1RPCUrl.Name),
SettlementRPCUrl: c.String(optionSettlementRPCUrl.Name),
Expand Down Expand Up @@ -305,3 +335,51 @@ func start(c *cli.Context) error {

return nil
}

func setupKeySigner(c *cli.Context) (keysigner.KeySigner, error) {
if c.IsSet(optionKeystorePath.Name) {
return setupKeystoreSigner(c)
}
return setupPrivateKeySigner(c)
}

func setupKeystoreSigner(c *cli.Context) (keysigner.KeySigner, error) {
// Load the keystore file
ks := keystore.NewKeyStore(c.String(optionKeystorePath.Name), keystore.LightScryptN, keystore.LightScryptP)
password := c.String(optionKeystorePassword.Name)
ksAccounts := ks.Accounts()

var account accounts.Account
if len(ksAccounts) == 0 {
var err error
account, err = ks.NewAccount(password)
if err != nil {
return nil, fmt.Errorf("failed to create account: %w", err)
}
} else {
account = ksAccounts[0]
}

fmt.Fprintf(c.App.Writer, "Public address of the key: %s\n", account.Address.Hex())
fmt.Fprintf(c.App.Writer, "Path of the secret key file: %s\n", account.URL.Path)

return keysigner.NewKeystoreSigner(ks, password, account), nil
}

func setupPrivateKeySigner(c *cli.Context) (keysigner.KeySigner, error) {
privKeyFile, err := resolveFilePath(c.String(optionPrivKeyFile.Name))
if err != nil {
return nil, fmt.Errorf("failed to get private key file path: %w", err)
}

if err := createKeyIfNotExists(c, privKeyFile); err != nil {
return nil, fmt.Errorf("failed to create private key: %w", err)
}

privKey, err := crypto.LoadECDSA(privKeyFile)
if err != nil {
return nil, fmt.Errorf("failed to load private key from file '%s': %w", privKeyFile, err)
}

return keysigner.NewPrivateKeySigner(privKey), nil
}
1 change: 1 addition & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
keystore_path: /keystore
log-level: debug
l1-rpc-url: <L1_URL>
settlement-rpc-url: http://sl-bootnode:8545
Expand Down
1 change: 1 addition & 0 deletions integrationtest/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions integrationtest/config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
priv_key_file: /key
keystore_path: /keystore
keystore_password: primev
log_level: debug
l1_rpc_url: <L1_URL>
settlement_rpc_url: http://sl-bootnode:8545
Expand Down
Original file line number Diff line number Diff line change
@@ -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}
Original file line number Diff line number Diff line change
@@ -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}
61 changes: 61 additions & 0 deletions pkg/keysigner/keysigner.go
Original file line number Diff line number Diff line change
@@ -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)
}
25 changes: 7 additions & 18 deletions pkg/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package node

import (
"context"
"crypto/ecdsa"
"database/sql"
"errors"
"fmt"
Expand All @@ -13,21 +12,20 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
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"
"github.com/primevprotocol/mev-oracle/pkg/updater"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/sha3"
)

type Options struct {
PrivateKey *ecdsa.PrivateKey
KeySigner keysigner.KeySigner
HTTPPort int
SettlementRPCUrl string
L1RPCUrl string
Expand Down Expand Up @@ -63,7 +61,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 {
Expand Down Expand Up @@ -114,7 +112,7 @@ func NewNode(opts *Options) (*Node, error) {
for _, winner := range opts.OverrideWinners {
err := setBuilderMapping(
ctx,
opts.PrivateKey,
opts.KeySigner,
chainID,
settlementClient,
oracleContract,
Expand Down Expand Up @@ -148,7 +146,7 @@ func NewNode(opts *Options) (*Node, error) {
updtrClosed := updtr.Start(ctx)

settlr := settler.NewSettler(
opts.PrivateKey,
opts.KeySigner,
chainID,
owner,
oracleContract,
Expand Down Expand Up @@ -234,15 +232,6 @@ func initDB(opts *Options) (db *sql.DB, err error) {
return db, err
}

func getEthAddressFromPubKey(key *ecdsa.PublicKey) common.Address {
pbBytes := crypto.FromECDSAPub(key)
hash := sha3.NewLegacyKeccak256()
hash.Write(pbBytes[1:])
address := hash.Sum(nil)[12:]

return common.BytesToAddress(address)
}

type laggerdL1Client struct {
l1Listener.EthClient
amount int
Expand Down Expand Up @@ -276,14 +265,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
}
Expand Down
Loading

0 comments on commit 13fa307

Please sign in to comment.