aboutsummaryrefslogblamecommitdiffstats
path: root/accounts/hd.go
blob: 75c47611061cc6018661cbe1905b74e77be3527e (plain) (tree)


















                                                                                  
                       









                                                                                  
                                                                                                   

                                                                                    

                                                                                      

                                                                                                      



                                                                                                        






























































































                                                                                                                                 
 
                                                                      
                                                          
                                          

 
                                                                     

                                                           
                     
                                                     

                          
                                            

                  
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package accounts

import (
    "encoding/json"
    "errors"
    "fmt"
    "math"
    "math/big"
    "strings"
)

// DefaultRootDerivationPath is the root path to which custom derivation endpoints
// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
// at m/44'/60'/0'/1, etc.
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}

// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
// at m/44'/60'/0'/0/1, etc.
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}

// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation
// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the
// second at m/44'/60'/0'/1, etc.
var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}

// DerivationPath represents the computer friendly version of a hierarchical
// deterministic wallet account derivaion path.
//
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
// defines derivation paths to be of the form:
//
//   m / purpose' / coin_type' / account' / change / address_index
//
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
// the `coin_type` 60' (or 0x8000003C) to Ethereum.
//
// The root path for Ethereum is m/44'/60'/0'/0 according to the specification
// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
// yet whether accounts should increment the last component or the children of
// that. We will go with the simpler approach of incrementing the last component.
type DerivationPath []uint32

// ParseDerivationPath converts a user specified derivation path string to the
// internal binary representation.
//
// Full derivation paths need to start with the `m/` prefix, relative derivation
// paths (which will get appended to the default root path) must not have prefixes
// in front of the first element. Whitespace is ignored.
func ParseDerivationPath(path string) (DerivationPath, error) {
    var result DerivationPath

    // Handle absolute or relative paths
    components := strings.Split(path, "/")
    switch {
    case len(components) == 0:
        return nil, errors.New("empty derivation path")

    case strings.TrimSpace(components[0]) == "":
        return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")

    case strings.TrimSpace(components[0]) == "m":
        components = components[1:]

    default:
        result = append(result, DefaultRootDerivationPath...)
    }
    // All remaining components are relative, append one by one
    if len(components) == 0 {
        return nil, errors.New("empty derivation path") // Empty relative paths
    }
    for _, component := range components {
        // Ignore any user added whitespace
        component = strings.TrimSpace(component)
        var value uint32

        // Handle hardened paths
        if strings.HasSuffix(component, "'") {
            value = 0x80000000
            component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
        }
        // Handle the non hardened component
        bigval, ok := new(big.Int).SetString(component, 0)
        if !ok {
            return nil, fmt.Errorf("invalid component: %s", component)
        }
        max := math.MaxUint32 - value
        if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
            if value == 0 {
                return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
            }
            return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
        }
        value += uint32(bigval.Uint64())

        // Append and repeat
        result = append(result, value)
    }
    return result, nil
}

// String implements the stringer interface, converting a binary derivation path
// to its canonical representation.
func (path DerivationPath) String() string {
    result := "m"
    for _, component := range path {
        var hardened bool
        if component >= 0x80000000 {
            component -= 0x80000000
            hardened = true
        }
        result = fmt.Sprintf("%s/%d", result, component)
        if hardened {
            result += "'"
        }
    }
    return result
}

// MarshalJSON turns a derivation path into its json-serialized string
func (path DerivationPath) MarshalJSON() ([]byte, error) {
    return json.Marshal(path.String())
}

// UnmarshalJSON a json-serialized string back into a derivation path
func (path *DerivationPath) UnmarshalJSON(b []byte) error {
    var dp string
    var err error
    if err = json.Unmarshal(b, &dp); err != nil {
        return err
    }
    *path, err = ParseDerivationPath(dp)
    return err
}