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:
parent
02047bf8bf
commit
6c72a79035
13
rpc/apis.go
13
rpc/apis.go
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
34
rpc/personal_api.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user