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) 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() } } id := uuid.NewRandom().String() accInfo := acc.ToGeneratedAccountInfo(id, 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 }