129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
package account
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/pborman/uuid"
|
|
"github.com/status-im/status-go/extkeys"
|
|
)
|
|
|
|
// ErrInvalidMnemonicPhraseLength is returned if the requested mnemonic length is invalid.
|
|
// Valid lengths are 12, 15, 18, 21, and 24.
|
|
var ErrInvalidMnemonicPhraseLength = errors.New("mnemonic phrase length; valid lengths are 12, 15, 18, 21, and 24")
|
|
|
|
// 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 := mnemonicPhraseLengthToEntropyStrenght(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 := hexutil.Encode(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
|
|
|
|
return address.Hex(), publicKeyHex, nil
|
|
}
|
|
|
|
func mnemonicPhraseLengthToEntropyStrenght(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
|
|
}
|