parsePath.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package hd
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "math"
  7. "math/big"
  8. "strings"
  9. )
  10. // DefaultRootDerivationPath is the root path to which custom derivation endpoints
  11. // are appended. As such, the first account will be at m/44'/60'/0'/0, the second
  12. // at m/44'/60'/0'/1, etc.
  13. var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
  14. // DefaultBaseDerivationPath is the base path from which custom derivation endpoints
  15. // are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
  16. // at m/44'/60'/0'/0/1, etc.
  17. var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
  18. // LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation
  19. // endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the
  20. // second at m/44'/60'/0'/1, etc.
  21. var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
  22. // DerivationPath represents the computer friendly version of a hierarchical
  23. // deterministic wallet account derivaion path.
  24. //
  25. // The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
  26. // defines derivation paths to be of the form:
  27. //
  28. // m / purpose' / coin_type' / account' / change / address_index
  29. //
  30. // The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
  31. // defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
  32. // SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
  33. // the `coin_type` 60' (or 0x8000003C) to Ethereum.
  34. //
  35. // The root path for Ethereum is m/44'/60'/0'/0 according to the specification
  36. // from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
  37. // yet whether accounts should increment the last component or the children of
  38. // that. We will go with the simpler approach of incrementing the last component.
  39. type DerivationPath []uint32
  40. // ParseDerivationPath converts a user specified derivation path string to the
  41. // internal binary representation.
  42. //
  43. // Full derivation paths need to start with the `m/` prefix, relative derivation
  44. // paths (which will get appended to the default root path) must not have prefixes
  45. // in front of the first element. Whitespace is ignored.
  46. func ParseDerivationPath(path string) (DerivationPath, error) {
  47. var result DerivationPath
  48. // Handle absolute or relative paths
  49. components := strings.Split(path, "/")
  50. switch {
  51. case len(components) == 0:
  52. return nil, errors.New("empty derivation path")
  53. case strings.TrimSpace(components[0]) == "":
  54. return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
  55. case strings.TrimSpace(components[0]) == "m":
  56. components = components[1:]
  57. default:
  58. result = append(result, DefaultRootDerivationPath...)
  59. }
  60. // All remaining components are relative, append one by one
  61. if len(components) == 0 {
  62. return nil, errors.New("empty derivation path") // Empty relative paths
  63. }
  64. for _, component := range components {
  65. // Ignore any user added whitespace
  66. component = strings.TrimSpace(component)
  67. var value uint32
  68. // Handle hardened paths
  69. if strings.HasSuffix(component, "'") {
  70. value = 0x80000000
  71. component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
  72. }
  73. // Handle the non hardened component
  74. bigval, ok := new(big.Int).SetString(component, 0)
  75. if !ok {
  76. return nil, fmt.Errorf("invalid component: %s", component)
  77. }
  78. max := math.MaxUint32 - value
  79. if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
  80. if value == 0 {
  81. return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
  82. }
  83. return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
  84. }
  85. value += uint32(bigval.Uint64())
  86. // Append and repeat
  87. result = append(result, value)
  88. }
  89. return result, nil
  90. }
  91. // String implements the stringer interface, converting a binary derivation path
  92. // to its canonical representation.
  93. func (path DerivationPath) String() string {
  94. result := "m"
  95. for _, component := range path {
  96. var hardened bool
  97. if component >= 0x80000000 {
  98. component -= 0x80000000
  99. hardened = true
  100. }
  101. result = fmt.Sprintf("%s/%d", result, component)
  102. if hardened {
  103. result += "'"
  104. }
  105. }
  106. return result
  107. }
  108. // MarshalJSON turns a derivation path into its json-serialized string
  109. func (path DerivationPath) MarshalJSON() ([]byte, error) {
  110. return json.Marshal(path.String())
  111. }
  112. // UnmarshalJSON a json-serialized string back into a derivation path
  113. func (path *DerivationPath) UnmarshalJSON(b []byte) error {
  114. var dp string
  115. var err error
  116. if err = json.Unmarshal(b, &dp); err != nil {
  117. return err
  118. }
  119. *path, err = ParseDerivationPath(dp)
  120. return err
  121. }
  122. func EthPath(index uint64) string {
  123. return fmt.Sprintf("m/44'/60'/0'/0/%d", index)
  124. }
  125. func TronPath(index uint64) string {
  126. return fmt.Sprintf("m/44'/195'/0'/0/%d", index)
  127. }