github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/accounts/hd.go (about)

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