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