github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/accounts/hd.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package accounts
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"math"
    24  	"math/big"
    25  	"strings"
    26  )
    27  
    28  // DefaultRootDerivationPath is the root path to which custom derivation endpoints
    29  // are appended. As such, the first account will be at m/44'/60'/0'/0, the second
    30  // at m/44'/60'/0'/1, etc.
    31  var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
    32  
    33  // DefaultBaseDerivationPath is the base path from which custom derivation endpoints
    34  // are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
    35  // at m/44'/60'/0'/0/1, etc.
    36  var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
    37  
    38  // LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation
    39  // endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the
    40  // second at m/44'/60'/0'/1, etc.
    41  var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
    42  
    43  // DerivationPath represents the computer friendly version of a hierarchical
    44  // deterministic wallet account derivation path.
    45  //
    46  // The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
    47  // defines derivation paths to be of the form:
    48  //
    49  //	m / purpose' / coin_type' / account' / change / address_index
    50  //
    51  // The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
    52  // defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
    53  // SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
    54  // the `coin_type` 60' (or 0x8000003C) to Ethereum.
    55  //
    56  // The root path for Ethereum is m/44'/60'/0'/0 according to the specification
    57  // from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
    58  // yet whether accounts should increment the last component or the children of
    59  // that. We will go with the simpler approach of incrementing the last component.
    60  type DerivationPath []uint32
    61  
    62  // ParseDerivationPath converts a user specified derivation path string to the
    63  // internal binary representation.
    64  //
    65  // Full derivation paths need to start with the `m/` prefix, relative derivation
    66  // paths (which will get appended to the default root path) must not have prefixes
    67  // in front of the first element. Whitespace is ignored.
    68  func ParseDerivationPath(path string) (DerivationPath, error) {
    69  	var result DerivationPath
    70  
    71  	// Handle absolute or relative paths
    72  	components := strings.Split(path, "/")
    73  	switch {
    74  	case len(components) == 0:
    75  		return nil, errors.New("empty derivation path")
    76  
    77  	case strings.TrimSpace(components[0]) == "":
    78  		return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
    79  
    80  	case strings.TrimSpace(components[0]) == "m":
    81  		components = components[1:]
    82  
    83  	default:
    84  		result = append(result, DefaultRootDerivationPath...)
    85  	}
    86  	// All remaining components are relative, append one by one
    87  	if len(components) == 0 {
    88  		return nil, errors.New("empty derivation path") // Empty relative paths
    89  	}
    90  	for _, component := range components {
    91  		// Ignore any user added whitespace
    92  		component = strings.TrimSpace(component)
    93  		var value uint32
    94  
    95  		// Handle hardened paths
    96  		if strings.HasSuffix(component, "'") {
    97  			value = 0x80000000
    98  			component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
    99  		}
   100  		// Handle the non hardened component
   101  		bigval, ok := new(big.Int).SetString(component, 0)
   102  		if !ok {
   103  			return nil, fmt.Errorf("invalid component: %s", component)
   104  		}
   105  		max := math.MaxUint32 - value
   106  		if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
   107  			if value == 0 {
   108  				return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
   109  			}
   110  			return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
   111  		}
   112  		value += uint32(bigval.Uint64())
   113  
   114  		// Append and repeat
   115  		result = append(result, value)
   116  	}
   117  	return result, nil
   118  }
   119  
   120  // String implements the stringer interface, converting a binary derivation path
   121  // to its canonical representation.
   122  func (path DerivationPath) String() string {
   123  	result := "m"
   124  	for _, component := range path {
   125  		var hardened bool
   126  		if component >= 0x80000000 {
   127  			component -= 0x80000000
   128  			hardened = true
   129  		}
   130  		result = fmt.Sprintf("%s/%d", result, component)
   131  		if hardened {
   132  			result += "'"
   133  		}
   134  	}
   135  	return result
   136  }
   137  
   138  // MarshalJSON turns a derivation path into its json-serialized string
   139  func (path DerivationPath) MarshalJSON() ([]byte, error) {
   140  	return json.Marshal(path.String())
   141  }
   142  
   143  // UnmarshalJSON a json-serialized string back into a derivation path
   144  func (path *DerivationPath) UnmarshalJSON(b []byte) error {
   145  	var dp string
   146  	var err error
   147  	if err = json.Unmarshal(b, &dp); err != nil {
   148  		return err
   149  	}
   150  	*path, err = ParseDerivationPath(dp)
   151  	return err
   152  }
   153  
   154  // DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component:
   155  // i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N.
   156  func DefaultIterator(base DerivationPath) func() DerivationPath {
   157  	path := make(DerivationPath, len(base))
   158  	copy(path[:], base[:])
   159  	// Set it back by one, so the first call gives the first result
   160  	path[len(path)-1]--
   161  	return func() DerivationPath {
   162  		path[len(path)-1]++
   163  		return path
   164  	}
   165  }
   166  
   167  // LedgerLiveIterator creates a bip44 path iterator for Ledger Live.
   168  // Ledger Live increments the third component rather than the fifth component
   169  // i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0.
   170  func LedgerLiveIterator(base DerivationPath) func() DerivationPath {
   171  	path := make(DerivationPath, len(base))
   172  	copy(path[:], base[:])
   173  	// Set it back by one, so the first call gives the first result
   174  	path[2]--
   175  	return func() DerivationPath {
   176  		// ledgerLivePathIterator iterates on the third component
   177  		path[2]++
   178  		return path
   179  	}
   180  }