760
vendor/github.com/status-im/status-go/account/accounts.go
generated
vendored
Normal file
760
vendor/github.com/status-im/status-go/account/accounts.go
generated
vendored
Normal file
@@ -0,0 +1,760 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/account/generator"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/keystore"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrAddressToAccountMappingFailure = errors.New("cannot retrieve a valid account for a given address")
|
||||
ErrAccountToKeyMappingFailure = errors.New("cannot retrieve a valid key for a given account")
|
||||
ErrNoAccountSelected = errors.New("no account has been selected, please login")
|
||||
ErrInvalidMasterKeyCreated = errors.New("can not create master extended key")
|
||||
ErrOnboardingNotStarted = errors.New("onboarding must be started before choosing an account")
|
||||
ErrOnboardingAccountNotFound = errors.New("cannot find onboarding account with the given id")
|
||||
ErrAccountKeyStoreMissing = errors.New("account key store is not set")
|
||||
ErrInvalidPersonalSignAccount = errors.New("invalid account as only the selected one can generate a signature")
|
||||
)
|
||||
|
||||
type ErrCannotLocateKeyFile struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e ErrCannotLocateKeyFile) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
var zeroAddress = types.Address{}
|
||||
|
||||
type SignParams struct {
|
||||
Data interface{} `json:"data"`
|
||||
Address string `json:"account"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
func (sp *SignParams) Validate(checkPassword bool) error {
|
||||
if len(sp.Address) != 2*types.AddressLength+2 {
|
||||
return errors.New("address has to be provided")
|
||||
}
|
||||
|
||||
if sp.Data == "" {
|
||||
return errors.New("data has to be provided")
|
||||
}
|
||||
|
||||
if checkPassword && sp.Password == "" {
|
||||
return errors.New("password has to be provided")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RecoverParams struct {
|
||||
Message string `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// Manager represents account manager interface
|
||||
type Manager interface {
|
||||
GetVerifiedWalletAccount(db *accounts.Database, address, password string) (*SelectedExtKey, error)
|
||||
Sign(rpcParams SignParams, verifiedAccount *SelectedExtKey) (result types.HexBytes, err error)
|
||||
CanRecover(rpcParams RecoverParams, revealedAddress types.Address) (bool, error)
|
||||
DeleteAccount(address types.Address) error
|
||||
}
|
||||
|
||||
// DefaultManager represents default account manager implementation
|
||||
type DefaultManager struct {
|
||||
mu sync.RWMutex
|
||||
rpcClient *rpc.Client
|
||||
rpcTimeout time.Duration
|
||||
Keydir string
|
||||
keystore types.KeyStore
|
||||
|
||||
accountsGenerator *generator.Generator
|
||||
onboarding *Onboarding
|
||||
|
||||
selectedChatAccount *SelectedExtKey // account that was processed during the last call to SelectAccount()
|
||||
mainAccountAddress types.Address
|
||||
watchAddresses []types.Address
|
||||
}
|
||||
|
||||
// GetKeystore is only used in tests
|
||||
func (m *DefaultManager) GetKeystore() types.KeyStore {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.keystore
|
||||
}
|
||||
|
||||
// AccountsGenerator returns accountsGenerator.
|
||||
func (m *DefaultManager) AccountsGenerator() *generator.Generator {
|
||||
return m.accountsGenerator
|
||||
}
|
||||
|
||||
// CreateAccount creates an internal geth account
|
||||
// BIP44-compatible keys are generated: CKD#1 is stored as account key, CKD#2 stored as sub-account root
|
||||
// Public key of CKD#1 is returned, with CKD#2 securely encoded into account key file (to be used for
|
||||
// sub-account derivations)
|
||||
func (m *DefaultManager) CreateAccount(password string) (generator.GeneratedAccountInfo, Info, string, error) {
|
||||
var mkInfo generator.GeneratedAccountInfo
|
||||
info := Info{}
|
||||
|
||||
// generate mnemonic phrase
|
||||
mn := extkeys.NewMnemonic()
|
||||
mnemonic, err := mn.MnemonicPhrase(extkeys.EntropyStrength128, extkeys.EnglishLanguage)
|
||||
if err != nil {
|
||||
return mkInfo, info, "", fmt.Errorf("can not create mnemonic seed: %v", err)
|
||||
}
|
||||
|
||||
// Generate extended master key (see BIP32)
|
||||
// We call extkeys.NewMaster with a seed generated with the 12 mnemonic words
|
||||
// but without using the optional password as an extra entropy as described in BIP39.
|
||||
// Future ideas/iterations in Status can add an an advanced options
|
||||
// for expert users, to be able to add a passphrase to the generation of the seed.
|
||||
extKey, err := extkeys.NewMaster(mn.MnemonicSeed(mnemonic, ""))
|
||||
if err != nil {
|
||||
return mkInfo, info, "", fmt.Errorf("can not create master extended key: %v", err)
|
||||
}
|
||||
|
||||
acc := generator.NewAccount(nil, extKey)
|
||||
mkInfo = acc.ToGeneratedAccountInfo("", mnemonic)
|
||||
|
||||
// import created key into account keystore
|
||||
info.WalletAddress, info.WalletPubKey, err = m.importExtendedKey(extkeys.KeyPurposeWallet, extKey, password)
|
||||
if err != nil {
|
||||
return mkInfo, info, "", err
|
||||
}
|
||||
|
||||
info.ChatAddress = info.WalletAddress
|
||||
info.ChatPubKey = info.WalletPubKey
|
||||
|
||||
return mkInfo, info, mnemonic, nil
|
||||
}
|
||||
|
||||
// RecoverAccount re-creates master key using given details.
|
||||
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
||||
func (m *DefaultManager) RecoverAccount(password, mnemonic string) (Info, error) {
|
||||
info := Info{}
|
||||
// re-create extended key (see BIP32)
|
||||
mn := extkeys.NewMnemonic()
|
||||
extKey, err := extkeys.NewMaster(mn.MnemonicSeed(mnemonic, ""))
|
||||
if err != nil {
|
||||
return info, ErrInvalidMasterKeyCreated
|
||||
}
|
||||
|
||||
// import re-created key into account keystore
|
||||
info.WalletAddress, info.WalletPubKey, err = m.importExtendedKey(extkeys.KeyPurposeWallet, extKey, password)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
info.ChatAddress = info.WalletAddress
|
||||
info.ChatPubKey = info.WalletPubKey
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
|
||||
// If no error is returned, then account is considered verified.
|
||||
func (m *DefaultManager) VerifyAccountPassword(keyStoreDir, address, password string) (*types.Key, error) {
|
||||
var err error
|
||||
var foundKeyFile []byte
|
||||
|
||||
addressObj := types.BytesToAddress(types.FromHex(address))
|
||||
checkAccountKey := func(path string, fileInfo os.FileInfo) error {
|
||||
if len(foundKeyFile) > 0 || fileInfo.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawKeyFile, e := ioutil.ReadFile(path)
|
||||
if e != nil {
|
||||
return fmt.Errorf("invalid account key file: %v", e)
|
||||
}
|
||||
|
||||
var accountKey struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
if e := json.Unmarshal(rawKeyFile, &accountKey); e != nil {
|
||||
return fmt.Errorf("failed to read key file: %s", e)
|
||||
}
|
||||
if types.HexToAddress("0x"+accountKey.Address).Hex() == addressObj.Hex() {
|
||||
foundKeyFile = rawKeyFile
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
// locate key within key store directory (address should be within the file)
|
||||
err = filepath.Walk(keyStoreDir, func(path string, fileInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkAccountKey(path, fileInfo)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot traverse key store folder: %v", err)
|
||||
}
|
||||
|
||||
if len(foundKeyFile) == 0 {
|
||||
return nil, &ErrCannotLocateKeyFile{fmt.Sprintf("cannot locate account for address: %s", addressObj.Hex())}
|
||||
}
|
||||
|
||||
key, err := keystore.DecryptKey(foundKeyFile, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// avoid swap attack
|
||||
if key.Address != addressObj {
|
||||
return nil, fmt.Errorf("account mismatch: have %s, want %s", key.Address.Hex(), addressObj.Hex())
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// SelectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
||||
// using provided password. Once verification is done, all previous identities are removed).
|
||||
func (m *DefaultManager) SelectAccount(loginParams LoginParams) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.accountsGenerator.Reset()
|
||||
|
||||
selectedChatAccount, err := m.unlockExtendedKey(loginParams.ChatAddress.String(), loginParams.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.watchAddresses = loginParams.WatchAddresses
|
||||
m.mainAccountAddress = loginParams.MainAccount
|
||||
m.selectedChatAccount = selectedChatAccount
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) SetAccountAddresses(main types.Address, secondary ...types.Address) {
|
||||
m.watchAddresses = []types.Address{main}
|
||||
m.watchAddresses = append(m.watchAddresses, secondary...)
|
||||
m.mainAccountAddress = main
|
||||
}
|
||||
|
||||
// SetChatAccount initializes selectedChatAccount with privKey
|
||||
func (m *DefaultManager) SetChatAccount(privKey *ecdsa.PrivateKey) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
address := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := &types.Key{
|
||||
ID: id,
|
||||
Address: address,
|
||||
PrivateKey: privKey,
|
||||
}
|
||||
|
||||
m.selectedChatAccount = &SelectedExtKey{
|
||||
Address: address,
|
||||
AccountKey: key,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MainAccountAddress returns main account address set during login
|
||||
func (m *DefaultManager) MainAccountAddress() (types.Address, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if m.mainAccountAddress == zeroAddress {
|
||||
return zeroAddress, ErrNoAccountSelected
|
||||
}
|
||||
|
||||
return m.mainAccountAddress, nil
|
||||
}
|
||||
|
||||
// WatchAddresses returns currently selected watch addresses.
|
||||
func (m *DefaultManager) WatchAddresses() []types.Address {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
return m.watchAddresses
|
||||
}
|
||||
|
||||
// SelectedChatAccount returns currently selected chat account
|
||||
func (m *DefaultManager) SelectedChatAccount() (*SelectedExtKey, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if m.selectedChatAccount == nil {
|
||||
return nil, ErrNoAccountSelected
|
||||
}
|
||||
return m.selectedChatAccount, nil
|
||||
}
|
||||
|
||||
// Logout clears selected accounts.
|
||||
func (m *DefaultManager) Logout() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.accountsGenerator.Reset()
|
||||
m.mainAccountAddress = zeroAddress
|
||||
m.watchAddresses = nil
|
||||
m.selectedChatAccount = nil
|
||||
}
|
||||
|
||||
// ImportAccount imports the account specified with privateKey.
|
||||
func (m *DefaultManager) ImportAccount(privateKey *ecdsa.PrivateKey, password string) (types.Address, error) {
|
||||
if m.keystore == nil {
|
||||
return types.Address{}, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
account, err := m.keystore.ImportECDSA(privateKey, password)
|
||||
|
||||
return account.Address, err
|
||||
}
|
||||
|
||||
// ImportSingleExtendedKey imports an extended key setting it in both the PrivateKey and ExtendedKey fields
|
||||
// of the Key struct.
|
||||
// ImportExtendedKey is used in older version of Status where PrivateKey is set to be the BIP44 key at index 0,
|
||||
// and ExtendedKey is the extended key of the BIP44 key at index 1.
|
||||
func (m *DefaultManager) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
||||
if m.keystore == nil {
|
||||
return "", "", ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
// imports extended key, create key file (if necessary)
|
||||
account, err := m.keystore.ImportSingleExtendedKey(extKey, password)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
address = account.Address.Hex()
|
||||
|
||||
// obtain public key to return
|
||||
account, key, err := m.keystore.AccountDecryptedKey(account, password)
|
||||
if err != nil {
|
||||
return address, "", err
|
||||
}
|
||||
|
||||
pubKey = types.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// importExtendedKey processes incoming extended key, extracts required info and creates corresponding account key.
|
||||
// Once account key is formed, that key is put (if not already) into keystore i.e. key is *encoded* into key file.
|
||||
func (m *DefaultManager) importExtendedKey(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
||||
if m.keystore == nil {
|
||||
return "", "", ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
// imports extended key, create key file (if necessary)
|
||||
account, err := m.keystore.ImportExtendedKeyForPurpose(keyPurpose, extKey, password)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
address = account.Address.Hex()
|
||||
|
||||
// obtain public key to return
|
||||
account, key, err := m.keystore.AccountDecryptedKey(account, password)
|
||||
if err != nil {
|
||||
return address, "", err
|
||||
}
|
||||
pubKey = types.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Accounts returns list of addresses for selected account, including
|
||||
// subaccounts.
|
||||
func (m *DefaultManager) Accounts() ([]types.Address, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
addresses := make([]types.Address, 0)
|
||||
if m.mainAccountAddress != zeroAddress {
|
||||
addresses = append(addresses, m.mainAccountAddress)
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// StartOnboarding starts the onboarding process generating accountsCount accounts and returns a slice of OnboardingAccount.
|
||||
func (m *DefaultManager) StartOnboarding(accountsCount, mnemonicPhraseLength int) ([]*OnboardingAccount, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
onboarding, err := NewOnboarding(accountsCount, mnemonicPhraseLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.onboarding = onboarding
|
||||
|
||||
return m.onboarding.Accounts(), nil
|
||||
}
|
||||
|
||||
// RemoveOnboarding reset the current onboarding struct setting it to nil and deleting the accounts from memory.
|
||||
func (m *DefaultManager) RemoveOnboarding() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.onboarding = nil
|
||||
}
|
||||
|
||||
// ImportOnboardingAccount imports the account specified by id and encrypts it with password.
|
||||
func (m *DefaultManager) ImportOnboardingAccount(id string, password string) (Info, string, error) {
|
||||
var info Info
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.onboarding == nil {
|
||||
return info, "", ErrOnboardingNotStarted
|
||||
}
|
||||
|
||||
acc, err := m.onboarding.Account(id)
|
||||
if err != nil {
|
||||
return info, "", err
|
||||
}
|
||||
|
||||
info, err = m.RecoverAccount(password, acc.mnemonic)
|
||||
if err != nil {
|
||||
return info, "", err
|
||||
}
|
||||
|
||||
m.onboarding = nil
|
||||
|
||||
return info, acc.mnemonic, nil
|
||||
}
|
||||
|
||||
// AddressToDecryptedAccount tries to load decrypted key for a given account.
|
||||
// The running node, has a keystore directory which is loaded on start. Key file
|
||||
// for a given address is expected to be in that directory prior to node start.
|
||||
func (m *DefaultManager) AddressToDecryptedAccount(address, password string) (types.Account, *types.Key, error) {
|
||||
if m.keystore == nil {
|
||||
return types.Account{}, nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
account, err := ParseAccountString(address)
|
||||
if err != nil {
|
||||
return types.Account{}, nil, ErrAddressToAccountMappingFailure
|
||||
}
|
||||
|
||||
account, key, err := m.keystore.AccountDecryptedKey(account, password)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s: %s", ErrAccountToKeyMappingFailure, err)
|
||||
}
|
||||
|
||||
return account, key, err
|
||||
}
|
||||
|
||||
func (m *DefaultManager) unlockExtendedKey(address, password string) (*SelectedExtKey, error) {
|
||||
account, accountKey, err := m.AddressToDecryptedAccount(address, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectedExtendedKey := &SelectedExtKey{
|
||||
Address: account.Address,
|
||||
AccountKey: accountKey,
|
||||
}
|
||||
|
||||
return selectedExtendedKey, nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) MigrateKeyStoreDir(oldDir, newDir string, addresses []string) error {
|
||||
paths := []string{}
|
||||
|
||||
addressesMap := map[string]struct{}{}
|
||||
for _, address := range addresses {
|
||||
addressesMap[address] = struct{}{}
|
||||
}
|
||||
|
||||
checkFile := func(path string, fileInfo os.FileInfo) error {
|
||||
if fileInfo.IsDir() || filepath.Dir(path) != oldDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawKeyFile, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid account key file: %v", err)
|
||||
}
|
||||
|
||||
var accountKey struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
if err := json.Unmarshal(rawKeyFile, &accountKey); err != nil {
|
||||
return fmt.Errorf("failed to read key file: %s", err)
|
||||
}
|
||||
|
||||
address := types.HexToAddress("0x" + accountKey.Address).Hex()
|
||||
if _, ok := addressesMap[address]; ok {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(oldDir, func(path string, fileInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkFile(path, fileInfo)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot traverse key store folder: %v", err)
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
_, fileName := filepath.Split(path)
|
||||
newPath := filepath.Join(newDir, fileName)
|
||||
err := os.Rename(path, newPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) ReEncryptKey(rawKey []byte, pass string, newPass string) (reEncryptedKey []byte, e error) {
|
||||
cryptoJSON, e := keystore.RawKeyToCryptoJSON(rawKey)
|
||||
if e != nil {
|
||||
return reEncryptedKey, fmt.Errorf("convert to crypto json error: %v", e)
|
||||
}
|
||||
|
||||
decryptedKey, e := keystore.DecryptKey(rawKey, pass)
|
||||
if e != nil {
|
||||
return reEncryptedKey, fmt.Errorf("decryption error: %v", e)
|
||||
}
|
||||
|
||||
if cryptoJSON.KDFParams["n"] == nil || cryptoJSON.KDFParams["p"] == nil {
|
||||
return reEncryptedKey, fmt.Errorf("Unable to determine `n` or `p`: %v", e)
|
||||
}
|
||||
n := int(cryptoJSON.KDFParams["n"].(float64))
|
||||
p := int(cryptoJSON.KDFParams["p"].(float64))
|
||||
|
||||
gethKey := gethkeystore.Key{
|
||||
Id: decryptedKey.ID,
|
||||
Address: gethcommon.Address(decryptedKey.Address),
|
||||
PrivateKey: decryptedKey.PrivateKey,
|
||||
ExtendedKey: decryptedKey.ExtendedKey,
|
||||
SubAccountIndex: decryptedKey.SubAccountIndex,
|
||||
}
|
||||
|
||||
return gethkeystore.EncryptKey(&gethKey, newPass, n, p)
|
||||
}
|
||||
|
||||
func (m *DefaultManager) ReEncryptKeyStoreDir(keyDirPath, oldPass, newPass string) error {
|
||||
rencryptFileAtPath := func(tempKeyDirPath, path string, fileInfo os.FileInfo) error {
|
||||
if fileInfo.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawKeyFile, e := ioutil.ReadFile(path)
|
||||
if e != nil {
|
||||
return fmt.Errorf("invalid account key file: %v", e)
|
||||
}
|
||||
|
||||
reEncryptedKey, e := m.ReEncryptKey(rawKeyFile, oldPass, newPass)
|
||||
if e != nil {
|
||||
return fmt.Errorf("unable to re-encrypt key file: %v, path: %s, name: %s", e, path, fileInfo.Name())
|
||||
}
|
||||
|
||||
tempWritePath := filepath.Join(tempKeyDirPath, fileInfo.Name())
|
||||
e = ioutil.WriteFile(tempWritePath, reEncryptedKey, fileInfo.Mode().Perm())
|
||||
if e != nil {
|
||||
return fmt.Errorf("unable write key file: %v", e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
keyDirPath = strings.TrimSuffix(keyDirPath, "/")
|
||||
keyDirPath = strings.TrimSuffix(keyDirPath, "\\")
|
||||
keyParent, keyDirName := filepath.Split(keyDirPath)
|
||||
|
||||
// backupKeyDirName used to store existing keys before final write
|
||||
backupKeyDirName := keyDirName + "-backup"
|
||||
// tempKeyDirName used to put re-encrypted keys
|
||||
tempKeyDirName := keyDirName + "-re-encrypted"
|
||||
backupKeyDirPath := filepath.Join(keyParent, backupKeyDirName)
|
||||
tempKeyDirPath := filepath.Join(keyParent, tempKeyDirName)
|
||||
|
||||
// create temp key dir
|
||||
err := os.MkdirAll(tempKeyDirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mkdirall error: %v, tempKeyDirPath: %s", err, tempKeyDirPath)
|
||||
}
|
||||
|
||||
err = filepath.Walk(keyDirPath, func(path string, fileInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
os.RemoveAll(tempKeyDirPath)
|
||||
return fmt.Errorf("walk callback error: %v", err)
|
||||
}
|
||||
|
||||
return rencryptFileAtPath(tempKeyDirPath, path, fileInfo)
|
||||
})
|
||||
if err != nil {
|
||||
os.RemoveAll(tempKeyDirPath)
|
||||
return fmt.Errorf("walk error: %v", err)
|
||||
}
|
||||
|
||||
// move existing keys
|
||||
err = os.Rename(keyDirPath, backupKeyDirPath)
|
||||
if err != nil {
|
||||
os.RemoveAll(tempKeyDirPath)
|
||||
return fmt.Errorf("unable to rename keyDirPath to backupKeyDirPath: %v", err)
|
||||
}
|
||||
|
||||
// move tempKeyDirPath to keyDirPath
|
||||
err = os.Rename(tempKeyDirPath, keyDirPath)
|
||||
if err != nil {
|
||||
// if this happens, then the app is probably bricked, because the keystore won't exist anymore
|
||||
// try to restore from backup
|
||||
_ = os.Rename(backupKeyDirPath, keyDirPath)
|
||||
return fmt.Errorf("unable to rename tempKeyDirPath to keyDirPath: %v", err)
|
||||
}
|
||||
|
||||
// remove temp and backup folders and their contents
|
||||
err = os.RemoveAll(tempKeyDirPath)
|
||||
if err != nil {
|
||||
// the re-encryption is complete so we don't throw
|
||||
log.Error("unable to delete tempKeyDirPath, manual cleanup required")
|
||||
}
|
||||
|
||||
err = os.RemoveAll(backupKeyDirPath)
|
||||
if err != nil {
|
||||
// the re-encryption is complete so we don't throw
|
||||
log.Error("unable to delete backupKeyDirPath, manual cleanup required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) DeleteAccount(address types.Address) error {
|
||||
return m.keystore.Delete(types.Account{Address: address})
|
||||
}
|
||||
|
||||
func (m *DefaultManager) GetVerifiedWalletAccount(db *accounts.Database, address, password string) (*SelectedExtKey, error) {
|
||||
exists, err := db.AddressExists(types.HexToAddress(address))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New("account doesn't exist")
|
||||
}
|
||||
|
||||
key, err := m.VerifyAccountPassword(m.Keydir, address, password)
|
||||
if _, ok := err.(*ErrCannotLocateKeyFile); ok {
|
||||
key, err = m.generatePartialAccountKey(db, address, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SelectedExtKey{
|
||||
Address: key.Address,
|
||||
AccountKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) generatePartialAccountKey(db *accounts.Database, address string, password string) (*types.Key, error) {
|
||||
dbPath, err := db.GetPath(types.HexToAddress(address))
|
||||
path := "m/" + dbPath[strings.LastIndex(dbPath, "/")+1:]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootAddress, err := db.GetWalletRootAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := m.AccountsGenerator().LoadAccount(rootAddress.Hex(), password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
masterID := info.ID
|
||||
|
||||
accInfosMap, err := m.AccountsGenerator().StoreDerivedAccounts(masterID, password, []string{path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, key, err := m.AddressToDecryptedAccount(accInfosMap[path].Address, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) Recover(rpcParams RecoverParams) (addr types.Address, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.rpcTimeout)
|
||||
defer cancel()
|
||||
var gethAddr gethcommon.Address
|
||||
err = m.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
ctx,
|
||||
&gethAddr,
|
||||
m.rpcClient.UpstreamChainID,
|
||||
params.PersonalRecoverMethodName,
|
||||
rpcParams.Message, rpcParams.Signature)
|
||||
addr = types.Address(gethAddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *DefaultManager) CanRecover(rpcParams RecoverParams, revealedAddress types.Address) (bool, error) {
|
||||
recovered, err := m.Recover(rpcParams)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return recovered == revealedAddress, nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) Sign(rpcParams SignParams, verifiedAccount *SelectedExtKey) (result types.HexBytes, err error) {
|
||||
if !strings.EqualFold(rpcParams.Address, verifiedAccount.Address.Hex()) {
|
||||
err = ErrInvalidPersonalSignAccount
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.rpcTimeout)
|
||||
defer cancel()
|
||||
var gethResult hexutil.Bytes
|
||||
err = m.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
ctx,
|
||||
&gethResult,
|
||||
m.rpcClient.UpstreamChainID,
|
||||
params.PersonalSignMethodName,
|
||||
rpcParams.Data, rpcParams.Address, rpcParams.Password)
|
||||
result = types.HexBytes(gethResult)
|
||||
|
||||
return
|
||||
}
|
||||
51
vendor/github.com/status-im/status-go/account/accounts_geth.go
generated
vendored
Normal file
51
vendor/github.com/status-im/status-go/account/accounts_geth.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
|
||||
"github.com/status-im/status-go/account/generator"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
// GethManager represents account manager interface.
|
||||
type GethManager struct {
|
||||
*DefaultManager
|
||||
|
||||
gethAccManager *accounts.Manager
|
||||
}
|
||||
|
||||
// NewGethManager returns new node account manager.
|
||||
func NewGethManager() *GethManager {
|
||||
m := &GethManager{}
|
||||
m.DefaultManager = &DefaultManager{accountsGenerator: generator.New(m)}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *GethManager) SetRPCClient(rpcClient *rpc.Client, rpcTimeout time.Duration) {
|
||||
m.DefaultManager.rpcClient = rpcClient
|
||||
m.DefaultManager.rpcTimeout = rpcTimeout
|
||||
}
|
||||
|
||||
// InitKeystore sets key manager and key store.
|
||||
func (m *GethManager) InitKeystore(keydir string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var err error
|
||||
m.gethAccManager, err = makeAccountManager(keydir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.keystore, err = makeKeyStore(m.gethAccManager)
|
||||
m.Keydir = keydir
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *GethManager) GetManager() *accounts.Manager {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.gethAccManager
|
||||
}
|
||||
23
vendor/github.com/status-im/status-go/account/address.go
generated
vendored
Normal file
23
vendor/github.com/status-im/status-go/account/address.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
func CreateAddress() (address, pubKey, privKey string, err error) {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
privKeyBytes := crypto.FromECDSA(key)
|
||||
pubKeyBytes := crypto.FromECDSAPub(&key.PublicKey)
|
||||
addressBytes := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
privKey = types.EncodeHex(privKeyBytes)
|
||||
pubKey = types.EncodeHex(pubKeyBytes)
|
||||
address = addressBytes.Hex()
|
||||
|
||||
return
|
||||
}
|
||||
43
vendor/github.com/status-im/status-go/account/generator/README.md
generated
vendored
Normal file
43
vendor/github.com/status-im/status-go/account/generator/README.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Account Generator
|
||||
|
||||
The Account Generator is used to generate, import, derive child keys, and store accounts.
|
||||
It is instantiated in the `account.Manager` struct and it's accessible from the `lib` and `mobile`
|
||||
package through functions with the `MultiAccount` prefix:
|
||||
|
||||
* MultiAccountGenerate
|
||||
* MultiAccountGenerateAndDeriveAddresses
|
||||
* MultiAccountImportMnemonic
|
||||
* MultiAccountDeriveAddresses
|
||||
* MultiAccountStoreDerivedAccounts
|
||||
* MultiAccountImportPrivateKey
|
||||
* MultiAccountStoreAccount
|
||||
* MultiAccountLoadAccount
|
||||
* MultiAccountReset
|
||||
|
||||
|
||||
Using `Generate` and `ImportMnemonic`, a master key is loaded in memory and a random temporarily id is returned.
|
||||
Bare in mind these accounts are not saved. They are in memory until `StoreAccount` or `StoreDerivedAccounts` are called.
|
||||
Calling `Reset` or restarting the application will remove everything from memory.
|
||||
Logging-in and Logging-out will do the same.
|
||||
|
||||
Since `Generate` and `ImportMnemonic` create extended keys, we can use those keys to derive new child keys.
|
||||
`MultiAccountDeriveAddresses(id, paths)` returns a list of addresses/pubKey, one for each path.
|
||||
This can be used to check balances on those addresses and show them to the user.
|
||||
|
||||
Once the user is happy with some specific derivation paths, we can store them using `StoreDerivedAccounts(id, passwordToEncryptKey, paths)`.
|
||||
`StoreDerivedAccounts` returns an address/pubKey for each path. The address can be use in the future to load them in memory again.
|
||||
Calling `StoreDerivedAccounts` will encrypt and store the keys, each one in a keystore json file, and remove all the keys from memory.
|
||||
Since they are derived from an extended key, they are extended keys too, so they can be used in the future to derive more child keys.
|
||||
`StoreAccount` stores the key identified by its ID, so in case the key comes from `Generate` or `ImportPrivateKey`, it will store the master key.
|
||||
In general we want to avoid saving master keys, so we should only use `StoreDerivedAccounts` for extended keys, and `StoreAccount` for normal keys.
|
||||
|
||||
Calling `Load(address, password)` will unlock the key specified by addresses using password, and load it in memory.
|
||||
`Load` returns a new id that can be used again with DeriveAddresses, `StoreAccount`, and `StoreDerivedAccounts`.
|
||||
|
||||
`ImportPrivateKey` imports a raw private key specified by its hex form.
|
||||
It's not an extended key, so it can't be used to derive child addresses.
|
||||
You can call `DeriveAddresses` to derive the address/pubKey of a normal key passing an empty string as derivation path.
|
||||
`StoreAccount` will save the key without deriving a child key.
|
||||
|
||||
|
||||
|
||||
171
vendor/github.com/status-im/status-go/account/generator/account.go
generated
vendored
Normal file
171
vendor/github.com/status-im/status-go/account/generator/account.go
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
accountJson "github.com/status-im/status-go/account/json"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
privateKey *ecdsa.PrivateKey
|
||||
extendedKey *extkeys.ExtendedKey
|
||||
}
|
||||
|
||||
func NewAccount(privateKey *ecdsa.PrivateKey, extKey *extkeys.ExtendedKey) Account {
|
||||
if privateKey == nil {
|
||||
privateKey = extKey.ToECDSA()
|
||||
}
|
||||
|
||||
return Account{
|
||||
privateKey: privateKey,
|
||||
extendedKey: extKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Account) ToAccountInfo() AccountInfo {
|
||||
privateKeyHex := types.EncodeHex(crypto.FromECDSA(a.privateKey))
|
||||
publicKeyHex := types.EncodeHex(crypto.FromECDSAPub(&a.privateKey.PublicKey))
|
||||
addressHex := crypto.PubkeyToAddress(a.privateKey.PublicKey).Hex()
|
||||
|
||||
return AccountInfo{
|
||||
PrivateKey: privateKeyHex,
|
||||
PublicKey: publicKeyHex,
|
||||
Address: addressHex,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Account) ToIdentifiedAccountInfo(id string) IdentifiedAccountInfo {
|
||||
info := a.ToAccountInfo()
|
||||
keyUID := sha256.Sum256(crypto.FromECDSAPub(&a.privateKey.PublicKey))
|
||||
keyUIDHex := types.EncodeHex(keyUID[:])
|
||||
return IdentifiedAccountInfo{
|
||||
AccountInfo: info,
|
||||
ID: id,
|
||||
KeyUID: keyUIDHex,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Account) ToGeneratedAccountInfo(id string, mnemonic string) GeneratedAccountInfo {
|
||||
idInfo := a.ToIdentifiedAccountInfo(id)
|
||||
return GeneratedAccountInfo{
|
||||
IdentifiedAccountInfo: idInfo,
|
||||
Mnemonic: mnemonic,
|
||||
}
|
||||
}
|
||||
|
||||
// AccountInfo contains a PublicKey and an Address of an account.
|
||||
type AccountInfo struct {
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func (a AccountInfo) MarshalJSON() ([]byte, error) {
|
||||
type Alias AccountInfo
|
||||
ext, err := accountJson.ExtendStructWithPubKeyData(a.PublicKey, Alias(a))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(ext)
|
||||
}
|
||||
|
||||
// IdentifiedAccountInfo contains AccountInfo and the ID of an account.
|
||||
type IdentifiedAccountInfo struct {
|
||||
AccountInfo
|
||||
ID string `json:"id"`
|
||||
// KeyUID is calculated as sha256 of the master public key and used for key
|
||||
// identification. This is the only information available about the master
|
||||
// key stored on a keycard before the card is paired.
|
||||
// KeyUID name is chosen over KeyID in order to make it consistent with
|
||||
// the name already used in Status and Keycard codebases.
|
||||
KeyUID string `json:"keyUid"`
|
||||
}
|
||||
|
||||
func (i IdentifiedAccountInfo) MarshalJSON() ([]byte, error) {
|
||||
accountInfoJSON, err := i.AccountInfo.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type info struct {
|
||||
ID string `json:"id"`
|
||||
KeyUID string `json:"keyUid"`
|
||||
}
|
||||
infoJSON, err := json.Marshal(info{
|
||||
ID: i.ID,
|
||||
KeyUID: i.KeyUID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoJSON[0] = ','
|
||||
return append(accountInfoJSON[:len(accountInfoJSON)-1], infoJSON...), nil
|
||||
}
|
||||
|
||||
func (i *IdentifiedAccountInfo) ToMultiAccount() *multiaccounts.Account {
|
||||
return &multiaccounts.Account{
|
||||
Timestamp: time.Now().Unix(),
|
||||
KeyUID: i.KeyUID,
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratedAccountInfo contains IdentifiedAccountInfo and the mnemonic of an account.
|
||||
type GeneratedAccountInfo struct {
|
||||
IdentifiedAccountInfo
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
}
|
||||
|
||||
func (g GeneratedAccountInfo) MarshalJSON() ([]byte, error) {
|
||||
accountInfoJSON, err := g.IdentifiedAccountInfo.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type info struct {
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
}
|
||||
infoJSON, err := json.Marshal(info{
|
||||
Mnemonic: g.Mnemonic,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoJSON[0] = ','
|
||||
return append(accountInfoJSON[:len(accountInfoJSON)-1], infoJSON...), nil
|
||||
}
|
||||
|
||||
func (g GeneratedAccountInfo) toGeneratedAndDerived(derived map[string]AccountInfo) GeneratedAndDerivedAccountInfo {
|
||||
return GeneratedAndDerivedAccountInfo{
|
||||
GeneratedAccountInfo: g,
|
||||
Derived: derived,
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratedAndDerivedAccountInfo contains GeneratedAccountInfo and derived AccountInfo mapped by derivation path.
|
||||
type GeneratedAndDerivedAccountInfo struct {
|
||||
GeneratedAccountInfo
|
||||
Derived map[string]AccountInfo `json:"derived"`
|
||||
}
|
||||
|
||||
func (g GeneratedAndDerivedAccountInfo) MarshalJSON() ([]byte, error) {
|
||||
accountInfoJSON, err := g.GeneratedAccountInfo.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type info struct {
|
||||
Derived map[string]AccountInfo `json:"derived"`
|
||||
}
|
||||
infoJSON, err := json.Marshal(info{
|
||||
Derived: g.Derived,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoJSON[0] = ','
|
||||
return append(accountInfoJSON[:len(accountInfoJSON)-1], infoJSON...), nil
|
||||
}
|
||||
352
vendor/github.com/status-im/status-go/account/generator/generator.go
generated
vendored
Normal file
352
vendor/github.com/status-im/status-go/account/generator/generator.go
generated
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/keystore"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAccountNotFoundByID is returned when the selected account doesn't exist in memory.
|
||||
ErrAccountNotFoundByID = errors.New("account not found")
|
||||
// ErrAccountCannotDeriveChildKeys is returned when trying to derive child accounts from a normal key.
|
||||
ErrAccountCannotDeriveChildKeys = errors.New("selected account cannot derive child keys")
|
||||
// ErrAccountManagerNotSet is returned when the account mananger instance is not set.
|
||||
ErrAccountManagerNotSet = errors.New("account manager not set")
|
||||
)
|
||||
|
||||
type AccountManager interface {
|
||||
AddressToDecryptedAccount(address, password string) (types.Account, *types.Key, error)
|
||||
ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error)
|
||||
ImportAccount(privateKey *ecdsa.PrivateKey, password string) (types.Address, error)
|
||||
}
|
||||
|
||||
type Generator struct {
|
||||
am AccountManager
|
||||
accounts map[string]*Account
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func New(am AccountManager) *Generator {
|
||||
return &Generator{
|
||||
am: am,
|
||||
accounts: make(map[string]*Account),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Generator) Generate(mnemonicPhraseLength int, n int, bip39Passphrase string) ([]GeneratedAccountInfo, error) {
|
||||
entropyStrength, err := MnemonicPhraseLengthToEntropyStrength(mnemonicPhraseLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos := make([]GeneratedAccountInfo, 0)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
mnemonic := extkeys.NewMnemonic()
|
||||
mnemonicPhrase, err := mnemonic.MnemonicPhrase(entropyStrength, extkeys.EnglishLanguage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not create mnemonic seed: %v", err)
|
||||
}
|
||||
|
||||
info, err := g.ImportMnemonic(mnemonicPhrase, bip39Passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
return infos, err
|
||||
}
|
||||
|
||||
func (g *Generator) CreateAccountFromPrivateKey(privateKeyHex string) (IdentifiedAccountInfo, error) {
|
||||
privateKeyHex = strings.TrimPrefix(privateKeyHex, "0x")
|
||||
privateKey, err := crypto.HexToECDSA(privateKeyHex)
|
||||
if err != nil {
|
||||
return IdentifiedAccountInfo{}, err
|
||||
}
|
||||
|
||||
acc := &Account{
|
||||
privateKey: privateKey,
|
||||
}
|
||||
|
||||
return acc.ToIdentifiedAccountInfo(""), nil
|
||||
}
|
||||
|
||||
func (g *Generator) ImportPrivateKey(privateKeyHex string) (IdentifiedAccountInfo, error) {
|
||||
privateKeyHex = strings.TrimPrefix(privateKeyHex, "0x")
|
||||
privateKey, err := crypto.HexToECDSA(privateKeyHex)
|
||||
if err != nil {
|
||||
return IdentifiedAccountInfo{}, err
|
||||
}
|
||||
|
||||
acc := &Account{
|
||||
privateKey: privateKey,
|
||||
}
|
||||
|
||||
id := g.addAccount(acc)
|
||||
|
||||
return acc.ToIdentifiedAccountInfo(id), nil
|
||||
}
|
||||
|
||||
func (g *Generator) ImportJSONKey(json string, password string) (IdentifiedAccountInfo, error) {
|
||||
key, err := keystore.DecryptKey([]byte(json), password)
|
||||
if err != nil {
|
||||
return IdentifiedAccountInfo{}, err
|
||||
}
|
||||
|
||||
acc := &Account{
|
||||
privateKey: key.PrivateKey,
|
||||
}
|
||||
|
||||
id := g.addAccount(acc)
|
||||
|
||||
return acc.ToIdentifiedAccountInfo(id), nil
|
||||
}
|
||||
|
||||
func (g *Generator) CreateAccountFromMnemonicAndDeriveAccountsForPaths(mnemonicPhrase string, bip39Passphrase string, paths []string) (GeneratedAndDerivedAccountInfo, error) {
|
||||
mnemonic := extkeys.NewMnemonic()
|
||||
masterExtendedKey, err := extkeys.NewMaster(mnemonic.MnemonicSeed(mnemonicPhrase, bip39Passphrase))
|
||||
if err != nil {
|
||||
return GeneratedAndDerivedAccountInfo{}, fmt.Errorf("can not create master extended key: %v", err)
|
||||
}
|
||||
|
||||
acc := &Account{
|
||||
privateKey: masterExtendedKey.ToECDSA(),
|
||||
extendedKey: masterExtendedKey,
|
||||
}
|
||||
|
||||
derivedAccountsInfo := make(map[string]AccountInfo)
|
||||
if len(paths) > 0 {
|
||||
derivedAccounts, err := g.deriveChildAccounts(acc, paths)
|
||||
if err != nil {
|
||||
return GeneratedAndDerivedAccountInfo{}, err
|
||||
}
|
||||
|
||||
for pathString, childAccount := range derivedAccounts {
|
||||
derivedAccountsInfo[pathString] = childAccount.ToAccountInfo()
|
||||
}
|
||||
}
|
||||
|
||||
accInfo := acc.ToGeneratedAccountInfo("", mnemonicPhrase)
|
||||
|
||||
return accInfo.toGeneratedAndDerived(derivedAccountsInfo), nil
|
||||
}
|
||||
|
||||
func (g *Generator) ImportMnemonic(mnemonicPhrase string, bip39Passphrase string) (GeneratedAccountInfo, error) {
|
||||
mnemonic := extkeys.NewMnemonic()
|
||||
masterExtendedKey, err := extkeys.NewMaster(mnemonic.MnemonicSeed(mnemonicPhrase, bip39Passphrase))
|
||||
if err != nil {
|
||||
return GeneratedAccountInfo{}, fmt.Errorf("can not create master extended key: %v", err)
|
||||
}
|
||||
|
||||
acc := &Account{
|
||||
privateKey: masterExtendedKey.ToECDSA(),
|
||||
extendedKey: masterExtendedKey,
|
||||
}
|
||||
|
||||
id := g.addAccount(acc)
|
||||
|
||||
return acc.ToGeneratedAccountInfo(id, mnemonicPhrase), nil
|
||||
}
|
||||
|
||||
func (g *Generator) GenerateAndDeriveAddresses(mnemonicPhraseLength int, n int, bip39Passphrase string, pathStrings []string) ([]GeneratedAndDerivedAccountInfo, error) {
|
||||
masterAccounts, err := g.Generate(mnemonicPhraseLength, n, bip39Passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accs := make([]GeneratedAndDerivedAccountInfo, n)
|
||||
|
||||
for i := 0; i < len(masterAccounts); i++ {
|
||||
acc := masterAccounts[i]
|
||||
derived, err := g.DeriveAddresses(acc.ID, pathStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accs[i] = acc.toGeneratedAndDerived(derived)
|
||||
}
|
||||
|
||||
return accs, nil
|
||||
}
|
||||
|
||||
func (g *Generator) DeriveAddresses(accountID string, pathStrings []string) (map[string]AccountInfo, error) {
|
||||
acc, err := g.findAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathAccounts, err := g.deriveChildAccounts(acc, pathStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathAccountsInfo := make(map[string]AccountInfo)
|
||||
|
||||
for pathString, childAccount := range pathAccounts {
|
||||
pathAccountsInfo[pathString] = childAccount.ToAccountInfo()
|
||||
}
|
||||
|
||||
return pathAccountsInfo, nil
|
||||
}
|
||||
|
||||
func (g *Generator) StoreAccount(accountID string, password string) (AccountInfo, error) {
|
||||
if g.am == nil {
|
||||
return AccountInfo{}, ErrAccountManagerNotSet
|
||||
}
|
||||
|
||||
acc, err := g.findAccount(accountID)
|
||||
if err != nil {
|
||||
return AccountInfo{}, err
|
||||
}
|
||||
|
||||
return g.store(acc, password)
|
||||
}
|
||||
|
||||
func (g *Generator) StoreDerivedAccounts(accountID string, password string, pathStrings []string) (map[string]AccountInfo, error) {
|
||||
if g.am == nil {
|
||||
return nil, ErrAccountManagerNotSet
|
||||
}
|
||||
|
||||
acc, err := g.findAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathAccounts, err := g.deriveChildAccounts(acc, pathStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathAccountsInfo := make(map[string]AccountInfo)
|
||||
|
||||
for pathString, childAccount := range pathAccounts {
|
||||
info, err := g.store(childAccount, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathAccountsInfo[pathString] = info
|
||||
}
|
||||
|
||||
return pathAccountsInfo, nil
|
||||
}
|
||||
|
||||
func (g *Generator) LoadAccount(address string, password string) (IdentifiedAccountInfo, error) {
|
||||
if g.am == nil {
|
||||
return IdentifiedAccountInfo{}, ErrAccountManagerNotSet
|
||||
}
|
||||
|
||||
_, key, err := g.am.AddressToDecryptedAccount(address, password)
|
||||
if err != nil {
|
||||
return IdentifiedAccountInfo{}, err
|
||||
}
|
||||
|
||||
if err := ValidateKeystoreExtendedKey(key); err != nil {
|
||||
return IdentifiedAccountInfo{}, err
|
||||
}
|
||||
|
||||
acc := &Account{
|
||||
privateKey: key.PrivateKey,
|
||||
extendedKey: key.ExtendedKey,
|
||||
}
|
||||
|
||||
id := g.addAccount(acc)
|
||||
|
||||
return acc.ToIdentifiedAccountInfo(id), nil
|
||||
}
|
||||
|
||||
func (g *Generator) deriveChildAccounts(acc *Account, pathStrings []string) (map[string]*Account, error) {
|
||||
pathAccounts := make(map[string]*Account)
|
||||
|
||||
for _, pathString := range pathStrings {
|
||||
childAccount, err := g.deriveChildAccount(acc, pathString)
|
||||
if err != nil {
|
||||
return pathAccounts, err
|
||||
}
|
||||
|
||||
pathAccounts[pathString] = childAccount
|
||||
}
|
||||
|
||||
return pathAccounts, nil
|
||||
}
|
||||
|
||||
func (g *Generator) deriveChildAccount(acc *Account, pathString string) (*Account, error) {
|
||||
_, path, err := decodePath(pathString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if acc.extendedKey.IsZeroed() && len(path) == 0 {
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
if acc.extendedKey.IsZeroed() {
|
||||
return nil, ErrAccountCannotDeriveChildKeys
|
||||
}
|
||||
|
||||
childExtendedKey, err := acc.extendedKey.Derive(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Account{
|
||||
privateKey: childExtendedKey.ToECDSA(),
|
||||
extendedKey: childExtendedKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *Generator) store(acc *Account, password string) (AccountInfo, error) {
|
||||
if acc.extendedKey != nil {
|
||||
if _, _, err := g.am.ImportSingleExtendedKey(acc.extendedKey, password); err != nil {
|
||||
return AccountInfo{}, err
|
||||
}
|
||||
} else {
|
||||
if _, err := g.am.ImportAccount(acc.privateKey, password); err != nil {
|
||||
return AccountInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return acc.ToAccountInfo(), nil
|
||||
}
|
||||
|
||||
func (g *Generator) addAccount(acc *Account) string {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
id := uuid.NewRandom().String()
|
||||
g.accounts[id] = acc
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Reset resets the accounts map removing all the accounts from memory.
|
||||
func (g *Generator) Reset() {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.accounts = make(map[string]*Account)
|
||||
}
|
||||
|
||||
func (g *Generator) findAccount(accountID string) (*Account, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
acc, ok := g.accounts[accountID]
|
||||
if !ok {
|
||||
return nil, ErrAccountNotFoundByID
|
||||
}
|
||||
|
||||
return acc, nil
|
||||
}
|
||||
226
vendor/github.com/status-im/status-go/account/generator/path_decoder.go
generated
vendored
Normal file
226
vendor/github.com/status-im/status-go/account/generator/path_decoder.go
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type startingPoint int
|
||||
|
||||
const (
|
||||
tokenMaster = 0x6D // char m
|
||||
tokenSeparator = 0x2F // char /
|
||||
tokenHardened = 0x27 // char '
|
||||
tokenDot = 0x2E // char .
|
||||
|
||||
hardenedStart = 0x80000000 // 2^31
|
||||
)
|
||||
|
||||
const (
|
||||
startingPointMaster startingPoint = iota + 1
|
||||
startingPointCurrent
|
||||
startingPointParent
|
||||
)
|
||||
|
||||
type parseFunc = func() error
|
||||
|
||||
type pathDecoder struct {
|
||||
s string
|
||||
r *strings.Reader
|
||||
f parseFunc
|
||||
pos int
|
||||
path []uint32
|
||||
start startingPoint
|
||||
currentToken string
|
||||
currentTokenHardened bool
|
||||
}
|
||||
|
||||
func newPathDecoder(path string) (*pathDecoder, error) {
|
||||
d := &pathDecoder{
|
||||
s: path,
|
||||
r: strings.NewReader(path),
|
||||
}
|
||||
|
||||
if err := d.reset(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *pathDecoder) reset() error {
|
||||
_, err := d.r.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.pos = 0
|
||||
d.start = startingPointCurrent
|
||||
d.f = d.parseStart
|
||||
d.path = make([]uint32, 0)
|
||||
d.resetCurrentToken()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *pathDecoder) resetCurrentToken() {
|
||||
d.currentToken = ""
|
||||
d.currentTokenHardened = false
|
||||
}
|
||||
|
||||
func (d *pathDecoder) parse() (startingPoint, []uint32, error) {
|
||||
for {
|
||||
err := d.f()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
} else {
|
||||
err = fmt.Errorf("error parsing derivation path %s; at position %d, %s", d.s, d.pos, err.Error())
|
||||
}
|
||||
|
||||
return d.start, d.path, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *pathDecoder) readByte() (byte, error) {
|
||||
b, err := d.r.ReadByte()
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
d.pos++
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (d *pathDecoder) unreadByte() error {
|
||||
err := d.r.UnreadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.pos--
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *pathDecoder) parseStart() error {
|
||||
b, err := d.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b == tokenMaster {
|
||||
d.start = startingPointMaster
|
||||
d.f = d.parseSeparator
|
||||
return nil
|
||||
}
|
||||
|
||||
if b == tokenDot {
|
||||
b2, err := d.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b2 == tokenDot {
|
||||
d.f = d.parseSeparator
|
||||
d.start = startingPointParent
|
||||
return nil
|
||||
}
|
||||
|
||||
d.f = d.parseSeparator
|
||||
d.start = startingPointCurrent
|
||||
return d.unreadByte()
|
||||
}
|
||||
|
||||
d.f = d.parseSegment
|
||||
|
||||
return d.unreadByte()
|
||||
}
|
||||
|
||||
func (d *pathDecoder) saveSegment() error {
|
||||
if len(d.currentToken) > 0 {
|
||||
i, err := strconv.ParseUint(d.currentToken, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i >= hardenedStart {
|
||||
d.pos -= len(d.currentToken) - 1
|
||||
return fmt.Errorf("index must be lower than 2^31, got %d", i)
|
||||
}
|
||||
|
||||
if d.currentTokenHardened {
|
||||
i += hardenedStart
|
||||
}
|
||||
|
||||
d.path = append(d.path, uint32(i))
|
||||
}
|
||||
|
||||
d.f = d.parseSegment
|
||||
d.resetCurrentToken()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *pathDecoder) parseSeparator() error {
|
||||
b, err := d.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b == tokenSeparator {
|
||||
return d.saveSegment()
|
||||
}
|
||||
|
||||
return fmt.Errorf("expected %s, got %s", string(rune(tokenSeparator)), string(rune(b)))
|
||||
}
|
||||
|
||||
func (d *pathDecoder) parseSegment() error {
|
||||
b, err := d.readByte()
|
||||
if err == io.EOF {
|
||||
if len(d.currentToken) == 0 {
|
||||
return fmt.Errorf("expected number, got EOF")
|
||||
}
|
||||
|
||||
if newErr := d.saveSegment(); newErr != nil {
|
||||
return newErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(d.currentToken) > 0 && b == tokenSeparator {
|
||||
return d.saveSegment()
|
||||
}
|
||||
|
||||
if len(d.currentToken) > 0 && b == tokenHardened {
|
||||
d.currentTokenHardened = true
|
||||
d.f = d.parseSeparator
|
||||
return nil
|
||||
}
|
||||
|
||||
if b < 0x30 || b > 0x39 {
|
||||
return fmt.Errorf("expected number, got %s", string(b))
|
||||
}
|
||||
|
||||
d.currentToken = fmt.Sprintf("%s%s", d.currentToken, string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodePath(str string) (startingPoint, []uint32, error) {
|
||||
d, err := newPathDecoder(str)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return d.parse()
|
||||
}
|
||||
46
vendor/github.com/status-im/status-go/account/generator/utils.go
generated
vendored
Normal file
46
vendor/github.com/status-im/status-go/account/generator/utils.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidKeystoreExtendedKey is returned when the decrypted keystore file
|
||||
// contains some old Status keys.
|
||||
// The old version used to store the BIP44 account at index 0 as PrivateKey,
|
||||
// and the BIP44 account at index 1 as ExtendedKey.
|
||||
// The current version stores the same key as PrivateKey and ExtendedKey.
|
||||
ErrInvalidKeystoreExtendedKey = errors.New("PrivateKey and ExtendedKey are different")
|
||||
ErrInvalidMnemonicPhraseLength = errors.New("invalid mnemonic phrase length; valid lengths are 12, 15, 18, 21, and 24")
|
||||
)
|
||||
|
||||
// ValidateKeystoreExtendedKey validates the keystore keys, checking that
|
||||
// ExtendedKey is the extended key of PrivateKey.
|
||||
func ValidateKeystoreExtendedKey(key *types.Key) error {
|
||||
if key.ExtendedKey.IsZeroed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !bytes.Equal(crypto.FromECDSA(key.PrivateKey), crypto.FromECDSA(key.ExtendedKey.ToECDSA())) {
|
||||
return ErrInvalidKeystoreExtendedKey
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MnemonicPhraseLengthToEntropyStrength returns the entropy strength for a given mnemonic length
|
||||
func MnemonicPhraseLengthToEntropyStrength(length int) (extkeys.EntropyStrength, error) {
|
||||
if length < 12 || length > 24 || length%3 != 0 {
|
||||
return 0, ErrInvalidMnemonicPhraseLength
|
||||
}
|
||||
|
||||
bitsLength := length * 11
|
||||
checksumLength := bitsLength % 32
|
||||
|
||||
return extkeys.EntropyStrength(bitsLength - checksumLength), nil
|
||||
}
|
||||
63
vendor/github.com/status-im/status-go/account/json/utils.go
generated
vendored
Normal file
63
vendor/github.com/status-im/status-go/account/json/utils.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/status-im/status-go/api/multiformat"
|
||||
"github.com/status-im/status-go/protocol/identity/emojihash"
|
||||
)
|
||||
|
||||
type PublicKeyData struct {
|
||||
CompressedKey string `json:"compressedKey"`
|
||||
EmojiHash []string `json:"emojiHash"`
|
||||
}
|
||||
|
||||
func getPubKeyData(publicKey string) (*PublicKeyData, error) {
|
||||
compressedKey, err := multiformat.SerializeLegacyKey(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
emojiHash, err := emojihash.GenerateFor(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PublicKeyData{compressedKey, emojiHash}, nil
|
||||
|
||||
}
|
||||
|
||||
func ExtendStructWithPubKeyData(publicKey string, item any) (any, error) {
|
||||
// If the public key is empty, do not attempt to extend the incoming item
|
||||
if publicKey == "" {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
pkd, err := getPubKeyData(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a struct with 2 embedded substruct fields in order to circumvent
|
||||
// "embedded field type cannot be a (pointer to a) type parameter"
|
||||
// compiler error that arises if we were to use a generic function instead
|
||||
typ := reflect.StructOf([]reflect.StructField{
|
||||
{
|
||||
Name: "Item",
|
||||
Anonymous: true,
|
||||
Type: reflect.TypeOf(item),
|
||||
},
|
||||
{
|
||||
Name: "Pkd",
|
||||
Anonymous: true,
|
||||
Type: reflect.TypeOf(pkd),
|
||||
},
|
||||
})
|
||||
|
||||
v := reflect.New(typ).Elem()
|
||||
v.Field(0).Set(reflect.ValueOf(item))
|
||||
v.Field(1).Set(reflect.ValueOf(pkd))
|
||||
s := v.Addr().Interface()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
41
vendor/github.com/status-im/status-go/account/keystore_geth.go
generated
vendored
Normal file
41
vendor/github.com/status-im/status-go/account/keystore_geth.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
|
||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
// makeAccountManager creates ethereum accounts.Manager with single disk backend and lightweight kdf.
|
||||
// If keydir is empty new temporary directory with go-ethereum-keystore will be intialized.
|
||||
func makeAccountManager(keydir string) (manager *accounts.Manager, err error) {
|
||||
if keydir == "" {
|
||||
// There is no datadir.
|
||||
keydir, err = os.MkdirTemp("", "go-ethereum-keystore")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(keydir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := accounts.Config{InsecureUnlockAllowed: false}
|
||||
return accounts.NewManager(&config, keystore.NewKeyStore(keydir, keystore.LightScryptN, keystore.LightScryptP)), nil
|
||||
}
|
||||
|
||||
func makeKeyStore(manager *accounts.Manager) (types.KeyStore, error) {
|
||||
backends := manager.Backends(keystore.KeyStoreType)
|
||||
if len(backends) == 0 {
|
||||
return nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
keyStore, ok := backends[0].(*keystore.KeyStore)
|
||||
if !ok {
|
||||
return nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
return gethbridge.WrapKeyStore(keyStore), nil
|
||||
}
|
||||
114
vendor/github.com/status-im/status-go/account/onboarding.go
generated
vendored
Normal file
114
vendor/github.com/status-im/status-go/account/onboarding.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"github.com/status-im/status-go/account/generator"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
)
|
||||
|
||||
// OnboardingAccount is returned during onboarding and contains its ID and the mnemonic to re-generate the same account Info keys.
|
||||
type OnboardingAccount struct {
|
||||
ID string `json:"id"`
|
||||
mnemonic string
|
||||
Info Info `json:"info"`
|
||||
}
|
||||
|
||||
// Onboarding is a struct contains a slice of OnboardingAccount.
|
||||
type Onboarding struct {
|
||||
accounts map[string]*OnboardingAccount
|
||||
}
|
||||
|
||||
// NewOnboarding returns a new onboarding struct generating n accounts.
|
||||
func NewOnboarding(n, mnemonicPhraseLength int) (*Onboarding, error) {
|
||||
onboarding := &Onboarding{
|
||||
accounts: make(map[string]*OnboardingAccount),
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
account, err := onboarding.generateAccount(mnemonicPhraseLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
onboarding.accounts[account.ID] = account
|
||||
}
|
||||
|
||||
return onboarding, nil
|
||||
}
|
||||
|
||||
// Accounts return the list of OnboardingAccount generated.
|
||||
func (o *Onboarding) Accounts() []*OnboardingAccount {
|
||||
accounts := make([]*OnboardingAccount, 0)
|
||||
for _, a := range o.accounts {
|
||||
accounts = append(accounts, a)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
// Account returns an OnboardingAccount by id.
|
||||
func (o *Onboarding) Account(id string) (*OnboardingAccount, error) {
|
||||
account, ok := o.accounts[id]
|
||||
if !ok {
|
||||
return nil, ErrOnboardingAccountNotFound
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (o *Onboarding) generateAccount(mnemonicPhraseLength int) (*OnboardingAccount, error) {
|
||||
entropyStrength, err := generator.MnemonicPhraseLengthToEntropyStrength(mnemonicPhraseLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mnemonic := extkeys.NewMnemonic()
|
||||
mnemonicPhrase, err := mnemonic.MnemonicPhrase(entropyStrength, extkeys.EnglishLanguage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not create mnemonic seed: %v", err)
|
||||
}
|
||||
|
||||
masterExtendedKey, err := extkeys.NewMaster(mnemonic.MnemonicSeed(mnemonicPhrase, ""))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not create master extended key: %v", err)
|
||||
}
|
||||
|
||||
walletAddress, walletPubKey, err := o.deriveAccount(masterExtendedKey, extkeys.KeyPurposeWallet, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := Info{
|
||||
WalletAddress: walletAddress,
|
||||
WalletPubKey: walletPubKey,
|
||||
ChatAddress: walletAddress,
|
||||
ChatPubKey: walletPubKey,
|
||||
}
|
||||
|
||||
uuid := uuid.NewRandom().String()
|
||||
|
||||
account := &OnboardingAccount{
|
||||
ID: uuid,
|
||||
mnemonic: mnemonicPhrase,
|
||||
Info: info,
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (o *Onboarding) deriveAccount(masterExtendedKey *extkeys.ExtendedKey, purpose extkeys.KeyPurpose, index uint32) (string, string, error) {
|
||||
extendedKey, err := masterExtendedKey.ChildForPurpose(purpose, index)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
privateKeyECDSA := extendedKey.ToECDSA()
|
||||
address := crypto.PubkeyToAddress(privateKeyECDSA.PublicKey)
|
||||
publicKeyHex := types.EncodeHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
|
||||
|
||||
return address.Hex(), publicKeyHex, nil
|
||||
}
|
||||
130
vendor/github.com/status-im/status-go/account/utils.go
generated
vendored
Normal file
130
vendor/github.com/status-im/status-go/account/utils.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
)
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrInvalidAccountAddressOrKey = errors.New("cannot parse address or key to valid account address")
|
||||
ErrInvalidMnemonicPhraseLength = errors.New("invalid mnemonic phrase length; valid lengths are 12, 15, 18, 21, and 24")
|
||||
)
|
||||
|
||||
type LoginParams struct {
|
||||
ChatAddress types.Address `json:"chatAddress"`
|
||||
Password string `json:"password"`
|
||||
MainAccount types.Address `json:"mainAccount"`
|
||||
WatchAddresses []types.Address `json:"watchAddresses"`
|
||||
MultiAccount *multiaccounts.Account `json:"multiAccount"`
|
||||
}
|
||||
|
||||
type ErrZeroAddress struct {
|
||||
field string
|
||||
}
|
||||
|
||||
func (e *ErrZeroAddress) Error() string {
|
||||
return fmt.Sprintf("%s contains an empty address", e.field)
|
||||
}
|
||||
|
||||
func newErrZeroAddress(field string) *ErrZeroAddress {
|
||||
return &ErrZeroAddress{
|
||||
field: field,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseLoginParams(paramsJSON string) (LoginParams, error) {
|
||||
var (
|
||||
params LoginParams
|
||||
zeroAddress types.Address
|
||||
)
|
||||
if err := json.Unmarshal([]byte(paramsJSON), ¶ms); err != nil {
|
||||
return params, err
|
||||
}
|
||||
|
||||
if params.ChatAddress == zeroAddress {
|
||||
return params, newErrZeroAddress("ChatAddress")
|
||||
}
|
||||
|
||||
if params.MainAccount == zeroAddress {
|
||||
return params, newErrZeroAddress("MainAccount")
|
||||
}
|
||||
|
||||
for _, address := range params.WatchAddresses {
|
||||
if address == zeroAddress {
|
||||
return params, newErrZeroAddress("WatchAddresses")
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// Info contains wallet and chat addresses and public keys of an account.
|
||||
type Info struct {
|
||||
WalletAddress string
|
||||
WalletPubKey string
|
||||
ChatAddress string
|
||||
ChatPubKey string
|
||||
}
|
||||
|
||||
// SelectedExtKey is a container for the selected (logged in) external account.
|
||||
type SelectedExtKey struct {
|
||||
Address types.Address
|
||||
AccountKey *types.Key
|
||||
SubAccounts []types.Account
|
||||
}
|
||||
|
||||
// Hex dumps address of a given extended key as hex string.
|
||||
func (k *SelectedExtKey) Hex() string {
|
||||
if k == nil {
|
||||
return "0x0"
|
||||
}
|
||||
|
||||
return k.Address.Hex()
|
||||
}
|
||||
|
||||
// ParseAccountString parses hex encoded string and returns is as types.Account.
|
||||
func ParseAccountString(account string) (types.Account, error) {
|
||||
// valid address, convert to account
|
||||
if types.IsHexAddress(account) {
|
||||
return types.Account{Address: types.HexToAddress(account)}, nil
|
||||
}
|
||||
|
||||
return types.Account{}, ErrInvalidAccountAddressOrKey
|
||||
}
|
||||
|
||||
// FromAddress converts account address from string to types.Address.
|
||||
// The function is useful to format "From" field of send transaction struct.
|
||||
func FromAddress(accountAddress string) types.Address {
|
||||
from, err := ParseAccountString(accountAddress)
|
||||
if err != nil {
|
||||
return types.Address{}
|
||||
}
|
||||
|
||||
return from.Address
|
||||
}
|
||||
|
||||
// ToAddress converts account address from string to *common.Address.
|
||||
// The function is useful to format "To" field of send transaction struct.
|
||||
func ToAddress(accountAddress string) *types.Address {
|
||||
to, err := ParseAccountString(accountAddress)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &to.Address
|
||||
}
|
||||
|
||||
func GetRandomMnemonic() (string, error) {
|
||||
// generate mnemonic phrase
|
||||
mn := extkeys.NewMnemonic()
|
||||
mnemonic, err := mn.MnemonicPhrase(extkeys.EntropyStrength128, extkeys.EnglishLanguage)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can not create mnemonic seed: %v", err)
|
||||
}
|
||||
return mnemonic, nil
|
||||
}
|
||||
Reference in New Issue
Block a user