forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
accounts, cmd, internal, node: implement HD wallet self-derivation
- Loading branch information
Showing
9 changed files
with
383 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// 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 ( | ||
"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} | ||
|
||
// 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, the second | ||
// at m/44'/60'/0'/1, etc. | ||
var DefaultBaseDerivationPath = 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// 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 ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
// Tests that HD derivation paths can be correctly parsed into our internal binary | ||
// representation. | ||
func TestHDPathParsing(t *testing.T) { | ||
tests := []struct { | ||
input string | ||
output DerivationPath | ||
}{ | ||
// Plain absolute derivation paths | ||
{"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
{"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, | ||
{"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
{"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, | ||
{"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
|
||
// Plain relative derivation paths | ||
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, | ||
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, | ||
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
|
||
// Hexadecimal absolute derivation paths | ||
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
{"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, | ||
{"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
{"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, | ||
{"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
|
||
// Hexadecimal relative derivation paths | ||
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, | ||
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, | ||
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||
|
||
// Weird inputs just to ensure they work | ||
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||
|
||
// Invaid derivation paths | ||
{"", nil}, // Empty relative derivation path | ||
{"m", nil}, // Empty absolute derivation path | ||
{"m/", nil}, // Missing last derivation component | ||
{"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error | ||
{"m/2147483648'", nil}, // Overflows 32 bit integer | ||
{"m/-1'", nil}, // Cannot contain negative number | ||
} | ||
for i, tt := range tests { | ||
if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) { | ||
t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output) | ||
} else if path == nil && err == nil { | ||
t.Errorf("test %d: nil path and error: %v", i, err) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.