status-im/status-go

View on GitHub
account/generator/generator.go

Summary

Maintainability
A
0 mins
Test Coverage
C
78%
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
}