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, ðsecp256k1.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 }