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 }