From b76c36fcdb3b4c16ed0610ae72df278b289991e5 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Mon, 6 Jul 2020 10:03:34 -0700 Subject: [PATCH] allow multikey unlock (#356) * allow multikey unlock * fix lint * Update rpc/apis.go Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- rpc/apis.go | 8 +++----- rpc/config.go | 34 ++++++++++++++++++++-------------- rpc/eth_api.go | 35 ++++++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/rpc/apis.go b/rpc/apis.go index d6bb2714..6082ac94 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -3,10 +3,8 @@ package rpc import ( - emintcrypto "github.com/cosmos/ethermint/crypto" - "github.com/cosmos/cosmos-sdk/client/context" - + emintcrypto "github.com/cosmos/ethermint/crypto" "github.com/ethereum/go-ethereum/rpc" ) @@ -21,7 +19,7 @@ const ( ) // GetRPCAPIs returns the list of all APIs -func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API { +func GetRPCAPIs(cliCtx context.CLIContext, keys []emintcrypto.PrivKeySecp256k1) []rpc.API { nonceLock := new(AddrLocker) backend := NewEthermintBackend(cliCtx) return []rpc.API{ @@ -34,7 +32,7 @@ func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []r { Namespace: EthNamespace, Version: apiVersion, - Service: NewPublicEthAPI(cliCtx, backend, nonceLock, key), + Service: NewPublicEthAPI(cliCtx, backend, nonceLock, keys), Public: true, }, { diff --git a/rpc/config.go b/rpc/config.go index 012a0243..5d3cd1ed 100644 --- a/rpc/config.go +++ b/rpc/config.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "strings" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -16,7 +17,6 @@ import ( "github.com/cosmos/ethermint/app" emintcrypto "github.com/cosmos/ethermint/crypto" - "github.com/ethereum/go-ethereum/rpc" "github.com/spf13/cobra" @@ -56,8 +56,9 @@ func EmintServeCmd(cdc *codec.Codec) *cobra.Command { func registerRoutes(rs *lcd.RestServer) { s := rpc.NewServer() accountName := viper.GetString(flagUnlockKey) + accountNames := strings.Split(accountName, ",") - var emintKey emintcrypto.PrivKeySecp256k1 + var emintKeys []emintcrypto.PrivKeySecp256k1 if len(accountName) > 0 { var err error inBuf := bufio.NewReader(os.Stdin) @@ -76,13 +77,13 @@ func registerRoutes(rs *lcd.RestServer) { } } - emintKey, err = unlockKeyFromNameAndPassphrase(accountName, passphrase) + emintKeys, err = unlockKeyFromNameAndPassphrase(accountNames, passphrase) if err != nil { panic(err) } } - apis := GetRPCAPIs(rs.CliCtx, emintKey) + apis := GetRPCAPIs(rs.CliCtx, emintKeys) // TODO: Allow cli to configure modules https://github.com/ChainSafe/ethermint/issues/74 whitelist := make(map[string]bool) @@ -105,7 +106,7 @@ func registerRoutes(rs *lcd.RestServer) { app.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux) } -func unlockKeyFromNameAndPassphrase(accountName, passphrase string) (emintKey emintcrypto.PrivKeySecp256k1, err error) { +func unlockKeyFromNameAndPassphrase(accountNames []string, passphrase string) (emintKeys []emintcrypto.PrivKeySecp256k1, err error) { keybase, err := keyring.NewKeyring( sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), @@ -116,16 +117,21 @@ func unlockKeyFromNameAndPassphrase(accountName, passphrase string) (emintKey em return } - // With keyring keybase, password is not required as it is pulled from the OS prompt - privKey, err := keybase.ExportPrivateKeyObject(accountName, passphrase) - if err != nil { - return - } + // try the for loop with array []string accountNames + // run through the bottom code inside the for loop + for _, acc := range accountNames { + // With keyring keybase, password is not required as it is pulled from the OS prompt + privKey, err := keybase.ExportPrivateKeyObject(acc, passphrase) + if err != nil { + return nil, err + } - var ok bool - emintKey, ok = privKey.(emintcrypto.PrivKeySecp256k1) - if !ok { - panic(fmt.Sprintf("invalid private key type: %T", privKey)) + var ok bool + emintKey, ok := privKey.(emintcrypto.PrivKeySecp256k1) + if !ok { + panic(fmt.Sprintf("invalid private key type: %T", privKey)) + } + emintKeys = append(emintKeys, emintKey) } return diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 8dbc2932..12a0a66e 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -45,19 +45,19 @@ import ( type PublicEthAPI struct { cliCtx context.CLIContext backend Backend - key emintcrypto.PrivKeySecp256k1 + keys []emintcrypto.PrivKeySecp256k1 nonceLock *AddrLocker keybaseLock sync.Mutex } // NewPublicEthAPI creates an instance of the public ETH Web3 API. func NewPublicEthAPI(cliCtx context.CLIContext, backend Backend, nonceLock *AddrLocker, - key emintcrypto.PrivKeySecp256k1) *PublicEthAPI { + key []emintcrypto.PrivKeySecp256k1) *PublicEthAPI { return &PublicEthAPI{ cliCtx: cliCtx, backend: backend, - key: key, + keys: key, nonceLock: nonceLock, } } @@ -282,15 +282,28 @@ func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber BlockNu return string(res), nil } +func checkKeyInKeyring(keys []emintcrypto.PrivKeySecp256k1, address common.Address) (key emintcrypto.PrivKeySecp256k1, exist bool) { + if len(keys) > 0 { + for _, key := range keys { + if bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) { + return key, true + } + } + } + return nil, false +} + // Sign signs the provided data using the private key of address via Geth's signature standard. func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { // TODO: Change this functionality to find an unlocked account by address - if e.key == nil || !bytes.Equal(e.key.PubKey().Address().Bytes(), address.Bytes()) { + + key, exist := checkKeyInKeyring(e.keys, address) + if !exist { return nil, keystore.ErrLocked } // Sign the requested hash with the wallet - signature, err := e.key.Sign(data) + signature, err := key.Sign(data) if err == nil { signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper } @@ -301,7 +314,9 @@ func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil // SendTransaction sends an Ethereum transaction. func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, error) { // TODO: Change this functionality to find an unlocked account by address - if e.key == nil || !bytes.Equal(e.key.PubKey().Address().Bytes(), args.From.Bytes()) { + + key, exist := checkKeyInKeyring(e.keys, args.From) + if !exist { return common.Hash{}, keystore.ErrLocked } @@ -326,7 +341,7 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err } // Sign transaction - if err := tx.Sign(intChainID, e.key.ToECDSA()); err != nil { + if err := tx.Sign(intChainID, key.ToECDSA()); err != nil { return common.Hash{}, err } @@ -429,9 +444,11 @@ func (e *PublicEthAPI) doCall( // Set sender address or use a default if none specified var addr common.Address + if args.From == nil { - if e.key != nil { - addr = common.BytesToAddress(e.key.PubKey().Address().Bytes()) + key, exist := checkKeyInKeyring(e.keys, *args.From) + if exist { + addr = common.BytesToAddress(key.PubKey().Address().Bytes()) } // No error handled here intentionally to match geth behaviour } else {