Skip to content

Commit

Permalink
accounts: fix Ledger Live account derivation path (clef) (ethereum#21757
Browse files Browse the repository at this point in the history
)

* 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>
  • Loading branch information
2 people authored and gzliudan committed Jan 9, 2025
1 parent 619c732 commit e1ceb71
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 0 deletions.
28 changes: 28 additions & 0 deletions accounts/hd.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,31 @@ func (path *DerivationPath) UnmarshalJSON(b []byte) error {
*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
}
}
39 changes: 39 additions & 0 deletions accounts/hd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package accounts

import (
"fmt"
"reflect"
"testing"
)
Expand Down Expand Up @@ -77,3 +78,41 @@ func TestHDPathParsing(t *testing.T) {
}
}
}

func testDerive(t *testing.T, next func() DerivationPath, expected []string) {
t.Helper()
for i, want := range expected {
if have := next(); fmt.Sprintf("%v", have) != want {
t.Errorf("step %d, have %v, want %v", i, have, want)
}
}
}

func TestHdPathIteration(t *testing.T) {
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
[]string{
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",
"m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3",
"m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5",
"m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7",
"m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9",
})

testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath),
[]string{
"m/44'/60'/0'/0", "m/44'/60'/0'/1",
"m/44'/60'/0'/2", "m/44'/60'/0'/3",
"m/44'/60'/0'/4", "m/44'/60'/0'/5",
"m/44'/60'/0'/6", "m/44'/60'/0'/7",
"m/44'/60'/0'/8", "m/44'/60'/0'/9",
})

testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath),
[]string{
"m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0",
"m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0",
"m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0",
"m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0",
"m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0",
})
}

0 comments on commit e1ceb71

Please sign in to comment.