mirror of
https://gitlab.com/pulsechaincom/go-pulse.git
synced 2024-12-22 19:40:36 +00:00
b71334ac3d
* signer/core/api: fix derivation of ledger live accounts For ledger hardware wallets, change account iteration as follows: - ledger legacy: m/44'/60'/0'/X; for 0<=X<5 - ledger live: m/44'/60'/0'/0/X; for 0<=X<5 - ledger legacy: m/44'/60'/0'/X; for 0<=X<10 - ledger live: m/44'/60'/X'/0/0; for 0<=X<10 Non-ledger derivation is unchanged and remains as: - non-ledger: m/44'/60'/0'/0/X; for 0<=X<10 * signer/core/api: derive ten default paths for all hardware wallets, plus ten legacy and ten live paths for ledger wallets * signer/core/api: as .../0'/0/0 already included by default paths, do not include it again with ledger live paths * accounts, signer: implement path iterators for hd wallets Co-authored-by: Martin Holst Swende <martin@swende.se>
181 lines
6.5 KiB
Go
181 lines
6.5 KiB
Go
// 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
|
|
}
|
|
|
|
// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component:
|
|
// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N.
|
|
func DefaultIterator(base DerivationPath) func() DerivationPath {
|
|
path := make(DerivationPath, len(base))
|
|
copy(path[:], base[:])
|
|
// Set it back by one, so the first call gives the first result
|
|
path[len(path)-1]--
|
|
return func() DerivationPath {
|
|
path[len(path)-1]++
|
|
return path
|
|
}
|
|
}
|
|
|
|
// LedgerLiveIterator creates a bip44 path iterator for Ledger Live.
|
|
// Ledger Live increments the third component rather than the fifth component
|
|
// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0.
|
|
func LedgerLiveIterator(base DerivationPath) func() DerivationPath {
|
|
path := make(DerivationPath, len(base))
|
|
copy(path[:], base[:])
|
|
// Set it back by one, so the first call gives the first result
|
|
path[2]--
|
|
return func() DerivationPath {
|
|
// ledgerLivePathIterator iterates on the third component
|
|
path[2]++
|
|
return path
|
|
}
|
|
}
|