github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/rpc/namespaces/personal/api.go (about)

     1  package personal
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/spf13/viper"
    11  
    12  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
    13  	"github.com/google/uuid"
    14  
    15  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys"
    16  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/mintkey"
    17  
    18  	"github.com/ethereum/go-ethereum/accounts"
    19  	"github.com/ethereum/go-ethereum/common"
    20  	"github.com/ethereum/go-ethereum/common/hexutil"
    21  	"github.com/ethereum/go-ethereum/crypto"
    22  
    23  	"github.com/fibonacci-chain/fbc/app/crypto/ethkeystore"
    24  	"github.com/fibonacci-chain/fbc/app/crypto/ethsecp256k1"
    25  	"github.com/fibonacci-chain/fbc/app/crypto/hd"
    26  	"github.com/fibonacci-chain/fbc/app/rpc/namespaces/eth"
    27  	rpctypes "github.com/fibonacci-chain/fbc/app/rpc/types"
    28  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/server"
    29  )
    30  
    31  // PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
    32  type PrivateAccountAPI struct {
    33  	ethAPI           *eth.PublicEthereumAPI
    34  	logger           log.Logger
    35  	keyInfos         []keys.Info // all keys, both locked and unlocked. unlocked keys are stored in ethAPI.keys
    36  	isExportKeystore bool
    37  }
    38  
    39  // NewAPI creates an instance of the public Personal Eth API.
    40  func NewAPI(ethAPI *eth.PublicEthereumAPI, log log.Logger) *PrivateAccountAPI {
    41  	api := &PrivateAccountAPI{
    42  		ethAPI:           ethAPI,
    43  		logger:           log.With("module", "json-rpc", "namespace", "personal"),
    44  		isExportKeystore: viper.GetBool(server.FlagExportKeystore),
    45  	}
    46  
    47  	err := api.ethAPI.GetKeyringInfo()
    48  	if err != nil {
    49  		return api
    50  	}
    51  	api.keyInfos, err = api.ethAPI.ClientCtx().Keybase.List()
    52  	if err != nil {
    53  		return api
    54  	}
    55  
    56  	return api
    57  }
    58  
    59  // ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
    60  // The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
    61  // keys stored on the keyring.
    62  // NOTE: The key will be both armored and encrypted using the same passphrase.
    63  func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
    64  	api.logger.Debug("personal_importRawKey")
    65  
    66  	priv, err := crypto.HexToECDSA(privkey)
    67  	if err != nil {
    68  		return common.Address{}, err
    69  	}
    70  
    71  	privKey := ethsecp256k1.PrivKey(crypto.FromECDSA(priv))
    72  	pubKey := privKey.PubKey()
    73  
    74  	// ignore error as we only care about the length of the list
    75  	list, _ := api.ethAPI.ClientCtx().Keybase.List()
    76  	for _, info := range list {
    77  		if info.GetPubKey().Equals(pubKey) {
    78  			return common.BytesToAddress(info.GetAddress().Bytes()), nil
    79  		}
    80  	}
    81  	privKeyName := fmt.Sprintf("personal_%s", uuid.New())
    82  	armor := mintkey.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
    83  
    84  	if err := api.ethAPI.ClientCtx().Keybase.ImportPrivKey(privKeyName, armor, password); err != nil {
    85  		return common.Address{}, err
    86  	}
    87  
    88  	addr := common.BytesToAddress(pubKey.Address().Bytes())
    89  
    90  	info, err := api.ethAPI.ClientCtx().Keybase.Get(privKeyName)
    91  	if err != nil {
    92  		return common.Address{}, err
    93  	}
    94  
    95  	// append key and info to be able to lock and list the account
    96  	//api.ethAPI.keys = append(api.ethAPI.keys, privKey)
    97  	api.keyInfos = append(api.keyInfos, info)
    98  	api.logger.Info("key successfully imported", "name", privKeyName, "address", addr.String())
    99  
   100  	return addr, nil
   101  }
   102  
   103  // ListAccounts will return a list of addresses for accounts this node manages.
   104  func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
   105  	api.logger.Debug("personal_listAccounts")
   106  	addrs := []common.Address{}
   107  	for _, info := range api.keyInfos {
   108  		addressBytes := info.GetPubKey().Address().Bytes()
   109  		addrs = append(addrs, common.BytesToAddress(addressBytes))
   110  	}
   111  
   112  	return addrs, nil
   113  }
   114  
   115  // LockAccount will lock the account associated with the given address when it's unlocked.
   116  // It removes the key corresponding to the given address from the API's local keys.
   117  func (api *PrivateAccountAPI) LockAccount(address common.Address) bool {
   118  	api.logger.Debug("personal_lockAccount", "address", address.String())
   119  
   120  	keys := api.ethAPI.GetKeys()
   121  	for i, key := range keys {
   122  		if !bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
   123  			continue
   124  		}
   125  
   126  		tmp := make([]ethsecp256k1.PrivKey, len(keys)-1)
   127  		copy(tmp[:i], keys[:i])
   128  		copy(tmp[i:], keys[i+1:])
   129  		api.ethAPI.SetKeys(tmp)
   130  
   131  		api.logger.Debug("account unlocked", "address", address.String())
   132  		return true
   133  	}
   134  
   135  	return false
   136  }
   137  
   138  // NewAccount will create a new account and returns the address for the new account.
   139  func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
   140  	api.logger.Debug("personal_newAccount")
   141  
   142  	name := "key_" + time.Now().UTC().Format(time.RFC3339) + uuid.New().String()
   143  	info, _, err := api.ethAPI.ClientCtx().Keybase.CreateMnemonic(name, keys.English, password, hd.EthSecp256k1, "")
   144  	if err != nil {
   145  		return common.Address{}, err
   146  	}
   147  
   148  	api.keyInfos = append(api.keyInfos, info)
   149  	addr := common.BytesToAddress(info.GetPubKey().Address().Bytes())
   150  
   151  	// export a private key as ethereum keystore
   152  	if api.isExportKeystore {
   153  		ksName, err := exportKeystoreFromKeybase(api.ethAPI.ClientCtx().Keybase, name, password)
   154  		if err != nil {
   155  			return common.Address{}, err
   156  		}
   157  		api.logger.Info("Please backup your eth keystore file", "path", ksName)
   158  	}
   159  
   160  	api.logger.Info("Your new key was generated", "address", addr.String())
   161  	api.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.fbchaind/"+name)
   162  	api.logger.Info("Please remember your password!")
   163  	return addr, nil
   164  }
   165  
   166  // exportKeystoreFromKeybase export a keybase.key to eth keystore.key
   167  func exportKeystoreFromKeybase(kb keys.Keybase, accName, password string) (string, error) {
   168  	// export tendermint private key
   169  	privKey, err := kb.ExportPrivateKeyObject(accName, password)
   170  	if err != nil {
   171  		return "", err
   172  	}
   173  	//create a keystore file to storage private key
   174  	keyDir, err := kb.FileDir()
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  	return ethkeystore.CreateKeystoreByTmKey(privKey, keyDir, password)
   179  }
   180  
   181  // UnlockAccount will unlock the account associated with the given address with
   182  // the given password for duration seconds. If duration is nil it will use a
   183  // default of 300 seconds. It returns an indication if the account was unlocked.
   184  // It exports the private key corresponding to the given address from the keyring and stores it in the API's local keys.
   185  func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Address, password string, _ *uint64) (bool, error) { // nolint: interfacer
   186  	api.logger.Debug("personal_unlockAccount", "address", addr.String())
   187  	// TODO: use duration
   188  
   189  	var keyInfo keys.Info
   190  
   191  	for _, info := range api.keyInfos {
   192  		addressBytes := info.GetPubKey().Address().Bytes()
   193  		if bytes.Equal(addressBytes, addr[:]) {
   194  			keyInfo = info
   195  			break
   196  		}
   197  	}
   198  
   199  	if keyInfo == nil {
   200  		return false, fmt.Errorf("cannot find key with given address %s", addr.String())
   201  	}
   202  
   203  	privKey, err := api.ethAPI.ClientCtx().Keybase.ExportPrivateKeyObject(keyInfo.GetName(), password)
   204  	if err != nil {
   205  		return false, err
   206  	}
   207  
   208  	ethermintPrivKey, ok := privKey.(ethsecp256k1.PrivKey)
   209  	if !ok {
   210  		return false, fmt.Errorf("invalid private key type %T, expected %T", privKey, &ethsecp256k1.PrivKey{})
   211  	}
   212  
   213  	api.ethAPI.SetKeys(append(api.ethAPI.GetKeys(), ethermintPrivKey))
   214  	api.logger.Debug("account unlocked", "address", addr.String())
   215  	return true, nil
   216  }
   217  
   218  // SendTransaction will create a transaction from the given arguments and
   219  // tries to sign it with the key associated with args.To. If the given password isn't
   220  // able to decrypt the key it fails.
   221  func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, _ string) (common.Hash, error) {
   222  	return api.ethAPI.SendTransaction(args)
   223  }
   224  
   225  // Sign calculates an Ethereum ECDSA signature for:
   226  // keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))
   227  //
   228  // Note, the produced signature conforms to the secp256k1 curve R, S and V values,
   229  // where the V value will be 27 or 28 for legacy reasons.
   230  //
   231  // The key used to calculate the signature is decrypted with the given password.
   232  //
   233  // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
   234  func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
   235  	api.logger.Debug("personal_sign", "data", data, "address", addr.String())
   236  
   237  	key, ok := rpctypes.GetKeyByAddress(api.ethAPI.GetKeys(), addr)
   238  	if !ok {
   239  		return nil, fmt.Errorf("cannot find key with address %s", addr.String())
   240  	}
   241  
   242  	sig, err := crypto.Sign(accounts.TextHash(data), key.ToECDSA())
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	sig[crypto.RecoveryIDOffset] += 27 // transform V from 0/1 to 27/28
   248  	return sig, nil
   249  }
   250  
   251  // EcRecover returns the address for the account that was used to create the signature.
   252  // Note, this function is compatible with eth_sign and personal_sign. As such it recovers
   253  // the address of:
   254  // hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
   255  // addr = ecrecover(hash, signature)
   256  //
   257  // Note, the signature must conform to the secp256k1 curve R, S and V values, where
   258  // the V value must be 27 or 28 for legacy reasons.
   259  //
   260  // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecove
   261  func (api *PrivateAccountAPI) EcRecover(_ context.Context, data, sig hexutil.Bytes) (common.Address, error) {
   262  	api.logger.Debug("personal_ecRecover", "data", data, "sig", sig)
   263  
   264  	if len(sig) != crypto.SignatureLength {
   265  		return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
   266  	}
   267  	if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 {
   268  		return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
   269  	}
   270  	sig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1
   271  
   272  	pubkey, err := crypto.SigToPub(accounts.TextHash(data), sig)
   273  	if err != nil {
   274  		return common.Address{}, err
   275  	}
   276  	return crypto.PubkeyToAddress(*pubkey), nil
   277  }