RPC unlock Ethermint key and eth_sign (#99)

* Set up personal account api for personal sign

* Added unlocking key functionality and attach to eth rpc

* Implemented eth_sign

* Transform V in sig based on yp and fix bug

* Fix lint issue

* Remove escape character from comment

* Switch error handling to panic on invalid unlocked key
This commit is contained in:
Austin Abell 2019-09-18 14:45:21 -04:00 committed by GitHub
parent 02047bf8bf
commit 6c72a79035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 18 deletions

View File

@ -4,21 +4,30 @@ package rpc
import ( import (
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
emintcrypto "github.com/cosmos/ethermint/crypto"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
// GetRPCAPIs returns the list of all APIs // GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(cliCtx context.CLIContext) []rpc.API { func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API {
return []rpc.API{ return []rpc.API{
{ {
Namespace: "web3", Namespace: "web3",
Version: "1.0", Version: "1.0",
Service: NewPublicWeb3API(), Service: NewPublicWeb3API(),
Public: true,
}, },
{ {
Namespace: "eth", Namespace: "eth",
Version: "1.0", Version: "1.0",
Service: NewPublicEthAPI(cliCtx), Service: NewPublicEthAPI(cliCtx, key),
Public: true,
},
{
Namespace: "personal",
Version: "1.0",
Service: NewPersonalEthAPI(cliCtx),
Public: false,
}, },
} }
} }

View File

@ -1,17 +1,21 @@
package rpc package rpc
import ( import (
"fmt"
"github.com/cosmos/cosmos-sdk/client/lcd" "github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
emintcrypto "github.com/cosmos/ethermint/crypto"
emintkeys "github.com/cosmos/ethermint/keys"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"log" "github.com/spf13/viper"
) )
// defaultModules returns all available modules const (
func defaultModules() []string { flagUnlockKey = "unlock-key"
return []string{"web3", "eth"} )
}
// Config contains configuration fields that determine the behavior of the RPC HTTP server. // Config contains configuration fields that determine the behavior of the RPC HTTP server.
// TODO: These may become irrelevant if HTTP config is handled by the SDK // TODO: These may become irrelevant if HTTP config is handled by the SDK
@ -30,31 +34,64 @@ type Config struct {
// Web3RpcCmd creates a CLI command to start RPC server // Web3RpcCmd creates a CLI command to start RPC server
func Web3RpcCmd(cdc *codec.Codec) *cobra.Command { func Web3RpcCmd(cdc *codec.Codec) *cobra.Command {
return lcd.ServeCommand(cdc, registerRoutes) cmd := lcd.ServeCommand(cdc, registerRoutes)
// Attach flag to cmd output to be handled in registerRoutes
cmd.Flags().String(flagUnlockKey, "", "Select a key to unlock on the RPC server")
return cmd
} }
// registerRoutes creates a new server and registers the `/rpc` endpoint. // registerRoutes creates a new server and registers the `/rpc` endpoint.
// Rpc calls are enabled based on their associated module (eg. "eth"). // Rpc calls are enabled based on their associated module (eg. "eth").
func registerRoutes(rs *lcd.RestServer) { func registerRoutes(rs *lcd.RestServer) {
s := rpc.NewServer() s := rpc.NewServer()
apis := GetRPCAPIs(rs.CliCtx) accountName := viper.GetString(flagUnlockKey)
var emintKey emintcrypto.PrivKeySecp256k1
if len(accountName) > 0 {
passphrase, err := emintkeys.GetPassphrase(accountName)
if err != nil {
panic(err)
}
emintKey, err = unlockKeyFromNameAndPassphrase(accountName, passphrase)
if err != nil {
panic(err)
}
}
apis := GetRPCAPIs(rs.CliCtx, emintKey)
// TODO: Allow cli to configure modules https://github.com/ChainSafe/ethermint/issues/74 // TODO: Allow cli to configure modules https://github.com/ChainSafe/ethermint/issues/74
modules := defaultModules()
whitelist := make(map[string]bool) whitelist := make(map[string]bool)
for _, module := range modules {
whitelist[module] = true
}
// Register all the APIs exposed by the services // Register all the APIs exposed by the services
for _, api := range apis { for _, api := range apis {
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
if err := s.RegisterName(api.Namespace, api.Service); err != nil { if err := s.RegisterName(api.Namespace, api.Service); err != nil {
log.Println(err) panic(err)
return
} }
} }
} }
rs.Mux.HandleFunc("/rpc", s.ServeHTTP).Methods("POST") rs.Mux.HandleFunc("/rpc", s.ServeHTTP).Methods("POST")
} }
func unlockKeyFromNameAndPassphrase(accountName, passphrase string) (emintKey emintcrypto.PrivKeySecp256k1, err error) {
keybase, err := emintkeys.NewKeyBaseFromHomeFlag()
if err != nil {
return
}
privKey, err := keybase.ExportPrivateKeyObject(accountName, passphrase)
if err != nil {
return
}
var ok bool
emintKey, ok = privKey.(emintcrypto.PrivKeySecp256k1)
if !ok {
panic(fmt.Sprintf("invalid private key type: %T", privKey))
}
return
}

View File

@ -1,13 +1,17 @@
package rpc package rpc
import ( import (
"bytes"
"fmt" "fmt"
"math/big" "math/big"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
emintcrypto "github.com/cosmos/ethermint/crypto"
emintkeys "github.com/cosmos/ethermint/keys" emintkeys "github.com/cosmos/ethermint/keys"
"github.com/cosmos/ethermint/version" "github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -17,12 +21,14 @@ import (
// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. // PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicEthAPI struct { type PublicEthAPI struct {
cliCtx context.CLIContext cliCtx context.CLIContext
key emintcrypto.PrivKeySecp256k1
} }
// NewPublicEthAPI creates an instance of the public ETH Web3 API. // NewPublicEthAPI creates an instance of the public ETH Web3 API.
func NewPublicEthAPI(cliCtx context.CLIContext) *PublicEthAPI { func NewPublicEthAPI(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) *PublicEthAPI {
return &PublicEthAPI{ return &PublicEthAPI{
cliCtx: cliCtx, cliCtx: cliCtx,
key: key,
} }
} }
@ -176,8 +182,19 @@ func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpc.BlockNumb
} }
// Sign signs the provided data using the private key of address via Geth's signature standard. // 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 { func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
return nil // TODO: Change this functionality to find an unlocked account by address
if e.key == nil || !bytes.Equal(e.key.PubKey().Address().Bytes(), address.Bytes()) {
return nil, keystore.ErrLocked
}
// Sign the requested hash with the wallet
signature, err := e.key.Sign(data)
if err == nil {
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
}
return signature, err
} }
// SendTransaction sends an Ethereum transaction. // SendTransaction sends an Ethereum transaction.

34
rpc/personal_api.go Normal file
View File

@ -0,0 +1,34 @@
package rpc
import (
"context"
sdkcontext "github.com/cosmos/cosmos-sdk/client/context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// PersonalEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PersonalEthAPI struct {
cliCtx sdkcontext.CLIContext
}
// NewPersonalEthAPI creates an instance of the public ETH Web3 API.
func NewPersonalEthAPI(cliCtx sdkcontext.CLIContext) *PersonalEthAPI {
return &PersonalEthAPI{
cliCtx: cliCtx,
}
}
// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The key used to calculate the signature is decrypted with the given password.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (e *PersonalEthAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
return nil, nil
}