github.com/0chain/gosdk@v1.17.11/zcncore/ethhdwallet/hdwallet.go (about)

     1  // Utilities to interact with ethereum wallet.
     2  package hdwallet
     3  
     4  import (
     5  	"crypto/ecdsa"
     6  	"crypto/rand"
     7  	"errors"
     8  	"fmt"
     9  	"math/big"
    10  	"os"
    11  	"sync"
    12  
    13  	"github.com/btcsuite/btcd/btcutil/hdkeychain"
    14  
    15  	"github.com/btcsuite/btcd/chaincfg"
    16  	ethereum "github.com/ethereum/go-ethereum"
    17  	"github.com/ethereum/go-ethereum/accounts"
    18  	"github.com/ethereum/go-ethereum/common"
    19  	"github.com/ethereum/go-ethereum/common/hexutil"
    20  	"github.com/ethereum/go-ethereum/core/types"
    21  	"github.com/ethereum/go-ethereum/crypto"
    22  	"github.com/tyler-smith/go-bip39"
    23  )
    24  
    25  // DefaultRootDerivationPath is the root path to which custom derivation endpoints
    26  // are appended. As such, the first account will be at m/44'/60'/0'/0, the second
    27  // at m/44'/60'/0'/1, etc.
    28  var DefaultRootDerivationPath = accounts.DefaultRootDerivationPath
    29  
    30  // DefaultBaseDerivationPath is the base path from which custom derivation endpoints
    31  // are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
    32  // at m/44'/60'/0'/1, etc
    33  var DefaultBaseDerivationPath = accounts.DefaultBaseDerivationPath
    34  
    35  const issue179FixEnvar = "GO_ETHEREUM_HDWALLET_FIX_ISSUE_179"
    36  
    37  // Wallet is the underlying wallet struct.
    38  type Wallet struct {
    39  	mnemonic    string
    40  	masterKey   *hdkeychain.ExtendedKey
    41  	seed        []byte
    42  	url         accounts.URL
    43  	paths       map[common.Address]accounts.DerivationPath
    44  	accounts    []accounts.Account
    45  	stateLock   sync.RWMutex
    46  	fixIssue172 bool
    47  }
    48  
    49  func newWallet(seed []byte) (*Wallet, error) {
    50  	masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	return &Wallet{
    56  		masterKey:   masterKey,
    57  		seed:        seed,
    58  		accounts:    []accounts.Account{},
    59  		paths:       map[common.Address]accounts.DerivationPath{},
    60  		fixIssue172: false || len(os.Getenv(issue179FixEnvar)) > 0,
    61  	}, nil
    62  }
    63  
    64  // NewFromMnemonic returns a new wallet from a BIP-39 mnemonic.
    65  func NewFromMnemonic(mnemonic string) (*Wallet, error) {
    66  	if mnemonic == "" {
    67  		return nil, errors.New("mnemonic is required")
    68  	}
    69  
    70  	if !bip39.IsMnemonicValid(mnemonic) {
    71  		return nil, errors.New("mnemonic is invalid")
    72  	}
    73  
    74  	seed, err := NewSeedFromMnemonic(mnemonic)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	wallet, err := newWallet(seed)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	wallet.mnemonic = mnemonic
    84  
    85  	return wallet, nil
    86  }
    87  
    88  // NewFromSeed returns a new wallet from a BIP-39 seed.
    89  func NewFromSeed(seed []byte) (*Wallet, error) {
    90  	if len(seed) == 0 {
    91  		return nil, errors.New("seed is required")
    92  	}
    93  
    94  	return newWallet(seed)
    95  }
    96  
    97  // URL implements accounts.Wallet, returning the URL of the device that
    98  // the wallet is on, however this does nothing since this is not a hardware device.
    99  func (w *Wallet) URL() accounts.URL {
   100  	return w.url
   101  }
   102  
   103  // Status implements accounts.Wallet, returning a custom status message
   104  // from the underlying vendor-specific hardware wallet implementation,
   105  // however this does nothing since this is not a hardware device.
   106  func (w *Wallet) Status() (string, error) {
   107  	return "ok", nil
   108  }
   109  
   110  // Open implements accounts.Wallet, however this does nothing since this
   111  // is not a hardware device.
   112  func (w *Wallet) Open(passphrase string) error {
   113  	return nil
   114  }
   115  
   116  // Close implements accounts.Wallet, however this does nothing since this
   117  // is not a hardware device.
   118  func (w *Wallet) Close() error {
   119  	return nil
   120  }
   121  
   122  // Accounts implements accounts.Wallet, returning the list of accounts pinned to
   123  // the wallet. If self-derivation was enabled, the account list is
   124  // periodically expanded based on current chain state.
   125  func (w *Wallet) Accounts() []accounts.Account {
   126  	// Attempt self-derivation if it's running
   127  	// Return whatever account list we ended up with
   128  	w.stateLock.RLock()
   129  	defer w.stateLock.RUnlock()
   130  
   131  	cpy := make([]accounts.Account, len(w.accounts))
   132  	copy(cpy, w.accounts)
   133  	return cpy
   134  }
   135  
   136  // Contains implements accounts.Wallet, returning whether a particular account is
   137  // or is not pinned into this wallet instance.
   138  func (w *Wallet) Contains(account accounts.Account) bool {
   139  	w.stateLock.RLock()
   140  	defer w.stateLock.RUnlock()
   141  
   142  	_, exists := w.paths[account.Address]
   143  	return exists
   144  }
   145  
   146  // Unpin unpins account from list of pinned accounts.
   147  func (w *Wallet) Unpin(account accounts.Account) error {
   148  	w.stateLock.RLock()
   149  	defer w.stateLock.RUnlock()
   150  
   151  	for i, acct := range w.accounts {
   152  		if acct.Address.String() == account.Address.String() {
   153  			w.accounts = removeAtIndex(w.accounts, i)
   154  			delete(w.paths, account.Address)
   155  			return nil
   156  		}
   157  	}
   158  
   159  	return errors.New("account not found")
   160  }
   161  
   162  // SetFixIssue172 determines whether the standard (correct) bip39
   163  // derivation path was used, or if derivation should be affected by
   164  // Issue172 [0] which was how this library was originally implemented.
   165  // [0] https://github.com/btcsuite/btcutil/pull/182/files
   166  func (w *Wallet) SetFixIssue172(fixIssue172 bool) {
   167  	w.fixIssue172 = fixIssue172
   168  }
   169  
   170  // Derive implements accounts.Wallet, deriving a new account at the specific
   171  // derivation path. If pin is set to true, the account will be added to the list
   172  // of tracked accounts.
   173  func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
   174  	// Try to derive the actual account and update its URL if successful
   175  	w.stateLock.RLock() // Avoid device disappearing during derivation
   176  
   177  	address, err := w.deriveAddress(path)
   178  
   179  	w.stateLock.RUnlock()
   180  
   181  	// If an error occurred or no pinning was requested, return
   182  	if err != nil {
   183  		return accounts.Account{}, err
   184  	}
   185  
   186  	account := accounts.Account{
   187  		Address: address,
   188  		URL: accounts.URL{
   189  			Scheme: "",
   190  			Path:   path.String(),
   191  		},
   192  	}
   193  
   194  	if !pin {
   195  		return account, nil
   196  	}
   197  
   198  	// Pinning needs to modify the state
   199  	w.stateLock.Lock()
   200  	defer w.stateLock.Unlock()
   201  
   202  	if _, ok := w.paths[address]; !ok {
   203  		w.accounts = append(w.accounts, account)
   204  		w.paths[address] = path
   205  	}
   206  
   207  	return account, nil
   208  }
   209  
   210  // SelfDerive implements accounts.Wallet, trying to discover accounts that the
   211  // user used previously (based on the chain state), but ones that he/she did not
   212  // explicitly pin to the wallet manually. To avoid chain head monitoring, self
   213  // derivation only runs during account listing (and even then throttled).
   214  func (w *Wallet) SelfDerive(base []accounts.DerivationPath, chain ethereum.ChainStateReader) {
   215  	// TODO: self derivation
   216  }
   217  
   218  // SignHash implements accounts.Wallet, which allows signing arbitrary data.
   219  func (w *Wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
   220  	// Make sure the requested account is contained within
   221  	path, ok := w.paths[account.Address]
   222  	if !ok {
   223  		return nil, accounts.ErrUnknownAccount
   224  	}
   225  
   226  	privateKey, err := w.derivePrivateKey(path)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	return crypto.Sign(hash, privateKey)
   232  }
   233  
   234  // SignTxEIP155 implements accounts.Wallet, which allows the account to sign an ERC-20 transaction.
   235  func (w *Wallet) SignTxEIP155(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
   236  	w.stateLock.RLock() // Comms have own mutex, this is for the state fields
   237  	defer w.stateLock.RUnlock()
   238  
   239  	// Make sure the requested account is contained within
   240  	path, ok := w.paths[account.Address]
   241  	if !ok {
   242  		return nil, accounts.ErrUnknownAccount
   243  	}
   244  
   245  	privateKey, err := w.derivePrivateKey(path)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	signer := types.NewEIP155Signer(chainID)
   251  	// Sign the transaction and verify the sender to avoid hardware fault surprises
   252  	signedTx, err := types.SignTx(tx, signer, privateKey)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	sender, err := types.Sender(signer, signedTx)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	if sender != account.Address {
   263  		return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex())
   264  	}
   265  
   266  	return signedTx, nil
   267  }
   268  
   269  // SignTx implements accounts.Wallet, which allows the account to sign an Ethereum transaction.
   270  func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
   271  	w.stateLock.RLock() // Comms have own mutex, this is for the state fields
   272  	defer w.stateLock.RUnlock()
   273  
   274  	// Make sure the requested account is contained within
   275  	path, ok := w.paths[account.Address]
   276  	if !ok {
   277  		return nil, accounts.ErrUnknownAccount
   278  	}
   279  
   280  	privateKey, err := w.derivePrivateKey(path)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	signer := types.HomesteadSigner{}
   286  	// Sign the transaction and verify the sender to avoid hardware fault surprises
   287  	signedTx, err := types.SignTx(tx, signer, privateKey)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	sender, err := types.Sender(signer, signedTx)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	if sender != account.Address {
   298  		return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex())
   299  	}
   300  
   301  	return signedTx, nil
   302  }
   303  
   304  // SignHashWithPassphrase implements accounts.Wallet, attempting
   305  // to sign the given hash with the given account using the
   306  // passphrase as extra authentication.
   307  func (w *Wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
   308  	return w.SignHash(account, hash)
   309  }
   310  
   311  // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
   312  // transaction with the given account using passphrase as extra authentication.
   313  func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
   314  	return w.SignTx(account, tx, chainID)
   315  }
   316  
   317  // PrivateKey returns the ECDSA private key of the account.
   318  func (w *Wallet) PrivateKey(account accounts.Account) (*ecdsa.PrivateKey, error) {
   319  	path, err := ParseDerivationPath(account.URL.Path)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  
   324  	return w.derivePrivateKey(path)
   325  }
   326  
   327  // PrivateKeyBytes returns the ECDSA private key in bytes format of the account.
   328  func (w *Wallet) PrivateKeyBytes(account accounts.Account) ([]byte, error) {
   329  	privateKey, err := w.PrivateKey(account)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	return crypto.FromECDSA(privateKey), nil
   335  }
   336  
   337  // PrivateKeyHex return the ECDSA private key in hex string format of the account.
   338  func (w *Wallet) PrivateKeyHex(account accounts.Account) (string, error) {
   339  	privateKeyBytes, err := w.PrivateKeyBytes(account)
   340  	if err != nil {
   341  		return "", err
   342  	}
   343  
   344  	return hexutil.Encode(privateKeyBytes)[2:], nil
   345  }
   346  
   347  // PublicKey returns the ECDSA public key of the account.
   348  func (w *Wallet) PublicKey(account accounts.Account) (*ecdsa.PublicKey, error) {
   349  	path, err := ParseDerivationPath(account.URL.Path)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  
   354  	return w.derivePublicKey(path)
   355  }
   356  
   357  // PublicKeyBytes returns the ECDSA public key in bytes format of the account.
   358  func (w *Wallet) PublicKeyBytes(account accounts.Account) ([]byte, error) {
   359  	publicKey, err := w.PublicKey(account)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  
   364  	return crypto.FromECDSAPub(publicKey), nil
   365  }
   366  
   367  // PublicKeyHex return the ECDSA public key in hex string format of the account.
   368  func (w *Wallet) PublicKeyHex(account accounts.Account) (string, error) {
   369  	publicKeyBytes, err := w.PublicKeyBytes(account)
   370  	if err != nil {
   371  		return "", err
   372  	}
   373  
   374  	return hexutil.Encode(publicKeyBytes)[4:], nil
   375  }
   376  
   377  // Address returns the address of the account.
   378  func (w *Wallet) Address(account accounts.Account) (common.Address, error) {
   379  	publicKey, err := w.PublicKey(account)
   380  	if err != nil {
   381  		return common.Address{}, err
   382  	}
   383  
   384  	return crypto.PubkeyToAddress(*publicKey), nil
   385  }
   386  
   387  // AddressBytes returns the address in bytes format of the account.
   388  func (w *Wallet) AddressBytes(account accounts.Account) ([]byte, error) {
   389  	address, err := w.Address(account)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	return address.Bytes(), nil
   394  }
   395  
   396  // AddressHex returns the address in hex string format of the account.
   397  func (w *Wallet) AddressHex(account accounts.Account) (string, error) {
   398  	address, err := w.Address(account)
   399  	if err != nil {
   400  		return "", err
   401  	}
   402  	return address.Hex(), nil
   403  }
   404  
   405  // Path return the derivation path of the account.
   406  func (w *Wallet) Path(account accounts.Account) (string, error) {
   407  	return account.URL.Path, nil
   408  }
   409  
   410  // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
   411  func (w *Wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
   412  	// Make sure the requested account is contained within
   413  	if !w.Contains(account) {
   414  		return nil, accounts.ErrUnknownAccount
   415  	}
   416  
   417  	return w.SignHash(account, crypto.Keccak256(data))
   418  }
   419  
   420  // SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed
   421  func (w *Wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
   422  	// Make sure the requested account is contained within
   423  	if !w.Contains(account) {
   424  		return nil, accounts.ErrUnknownAccount
   425  	}
   426  
   427  	return w.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
   428  }
   429  
   430  // SignText requests the wallet to sign the hash of a given piece of data, prefixed
   431  // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
   432  // the account in a keystore).
   433  func (w *Wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
   434  	// Make sure the requested account is contained within
   435  	if !w.Contains(account) {
   436  		return nil, accounts.ErrUnknownAccount
   437  	}
   438  
   439  	return w.SignHash(account, accounts.TextHash(text))
   440  }
   441  
   442  // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the
   443  // given text (which is hashed) with the given account using passphrase as extra authentication.
   444  func (w *Wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
   445  	// Make sure the requested account is contained within
   446  	if !w.Contains(account) {
   447  		return nil, accounts.ErrUnknownAccount
   448  	}
   449  
   450  	return w.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
   451  }
   452  
   453  // ParseDerivationPath parses the derivation path in string format into []uint32
   454  func ParseDerivationPath(path string) (accounts.DerivationPath, error) {
   455  	return accounts.ParseDerivationPath(path)
   456  }
   457  
   458  // MustParseDerivationPath parses the derivation path in string format into
   459  // []uint32 but will panic if it can't parse it.
   460  func MustParseDerivationPath(path string) accounts.DerivationPath {
   461  	parsed, err := accounts.ParseDerivationPath(path)
   462  	if err != nil {
   463  		panic(err)
   464  	}
   465  
   466  	return parsed
   467  }
   468  
   469  // NewMnemonic returns a randomly generated BIP-39 mnemonic using 128-256 bits of entropy.
   470  func NewMnemonic(bits int) (string, error) {
   471  	entropy, err := bip39.NewEntropy(bits)
   472  	if err != nil {
   473  		return "", err
   474  	}
   475  	return bip39.NewMnemonic(entropy)
   476  }
   477  
   478  // NewMnemonicFromEntropy returns a BIP-39 mnemonic from entropy.
   479  func NewMnemonicFromEntropy(entropy []byte) (string, error) {
   480  	return bip39.NewMnemonic(entropy)
   481  }
   482  
   483  // NewEntropy returns a randomly generated entropy.
   484  func NewEntropy(bits int) ([]byte, error) {
   485  	return bip39.NewEntropy(bits)
   486  }
   487  
   488  // NewSeed returns a randomly generated BIP-39 seed.
   489  func NewSeed() ([]byte, error) {
   490  	b := make([]byte, 64)
   491  	_, err := rand.Read(b)
   492  	return b, err
   493  }
   494  
   495  // NewSeedFromMnemonic returns a BIP-39 seed based on a BIP-39 mnemonic.
   496  func NewSeedFromMnemonic(mnemonic string) ([]byte, error) {
   497  	if mnemonic == "" {
   498  		return nil, errors.New("mnemonic is required")
   499  	}
   500  
   501  	return bip39.NewSeedWithErrorChecking(mnemonic, "")
   502  }
   503  
   504  // DerivePrivateKey derives the private key of the derivation path.
   505  func (w *Wallet) derivePrivateKey(path accounts.DerivationPath) (*ecdsa.PrivateKey, error) {
   506  	var err error
   507  	key := w.masterKey
   508  	for _, n := range path {
   509  		if w.fixIssue172 && key.IsAffectedByIssue172() {
   510  			key, err = key.Derive(n)
   511  		} else {
   512  			key, err = key.DeriveNonStandard(n) //nolint
   513  		}
   514  		if err != nil {
   515  			return nil, err
   516  		}
   517  	}
   518  
   519  	privateKey, err := key.ECPrivKey()
   520  	privateKeyECDSA := privateKey.ToECDSA()
   521  	if err != nil {
   522  		return nil, err
   523  	}
   524  
   525  	return privateKeyECDSA, nil
   526  }
   527  
   528  // DerivePublicKey derives the public key of the derivation path.
   529  func (w *Wallet) derivePublicKey(path accounts.DerivationPath) (*ecdsa.PublicKey, error) {
   530  	privateKeyECDSA, err := w.derivePrivateKey(path)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  
   535  	publicKey := privateKeyECDSA.Public()
   536  	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
   537  	if !ok {
   538  		return nil, errors.New("failed to get public key")
   539  	}
   540  
   541  	return publicKeyECDSA, nil
   542  }
   543  
   544  // DeriveAddress derives the account address of the derivation path.
   545  func (w *Wallet) deriveAddress(path accounts.DerivationPath) (common.Address, error) {
   546  	publicKeyECDSA, err := w.derivePublicKey(path)
   547  	if err != nil {
   548  		return common.Address{}, err
   549  	}
   550  
   551  	address := crypto.PubkeyToAddress(*publicKeyECDSA)
   552  	return address, nil
   553  }
   554  
   555  // removeAtIndex removes an account at index.
   556  func removeAtIndex(accts []accounts.Account, index int) []accounts.Account {
   557  	return append(accts[:index], accts[index+1:]...)
   558  }