accounts, signer: fix Ledger Live account derivation path (clef) (#21757)
* 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>
This commit is contained in:
parent
fa572cd297
commit
b71334ac3d
@ -150,3 +150,31 @@ func (path *DerivationPath) UnmarshalJSON(b []byte) error {
|
|||||||
*path, err = ParseDerivationPath(dp)
|
*path, err = ParseDerivationPath(dp)
|
||||||
return err
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -322,62 +322,65 @@ func (api *SignerAPI) openTrezor(url accounts.URL) {
|
|||||||
|
|
||||||
// startUSBListener starts a listener for USB events, for hardware wallet interaction
|
// startUSBListener starts a listener for USB events, for hardware wallet interaction
|
||||||
func (api *SignerAPI) startUSBListener() {
|
func (api *SignerAPI) startUSBListener() {
|
||||||
events := make(chan accounts.WalletEvent, 16)
|
eventCh := make(chan accounts.WalletEvent, 16)
|
||||||
am := api.am
|
am := api.am
|
||||||
am.Subscribe(events)
|
am.Subscribe(eventCh)
|
||||||
go func() {
|
// Open any wallets already attached
|
||||||
|
for _, wallet := range am.Wallets() {
|
||||||
|
if err := wallet.Open(""); err != nil {
|
||||||
|
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
|
||||||
|
if err == usbwallet.ErrTrezorPINNeeded {
|
||||||
|
go api.openTrezor(wallet.URL())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go api.derivationLoop(eventCh)
|
||||||
|
}
|
||||||
|
|
||||||
// Open any wallets already attached
|
// derivationLoop listens for wallet events
|
||||||
for _, wallet := range am.Wallets() {
|
func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) {
|
||||||
if err := wallet.Open(""); err != nil {
|
// Listen for wallet event till termination
|
||||||
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
|
for event := range events {
|
||||||
|
switch event.Kind {
|
||||||
|
case accounts.WalletArrived:
|
||||||
|
if err := event.Wallet.Open(""); err != nil {
|
||||||
|
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
|
||||||
if err == usbwallet.ErrTrezorPINNeeded {
|
if err == usbwallet.ErrTrezorPINNeeded {
|
||||||
go api.openTrezor(wallet.URL())
|
go api.openTrezor(event.Wallet.URL())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
case accounts.WalletOpened:
|
||||||
// Listen for wallet event till termination
|
status, _ := event.Wallet.Status()
|
||||||
for event := range events {
|
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
||||||
switch event.Kind {
|
var derive = func(limit int, next func() accounts.DerivationPath) {
|
||||||
case accounts.WalletArrived:
|
// Derive first N accounts, hardcoded for now
|
||||||
if err := event.Wallet.Open(""); err != nil {
|
for i := 0; i < limit; i++ {
|
||||||
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
|
path := next()
|
||||||
if err == usbwallet.ErrTrezorPINNeeded {
|
if acc, err := event.Wallet.Derive(path, true); err != nil {
|
||||||
go api.openTrezor(event.Wallet.URL())
|
log.Warn("Account derivation failed", "error", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Derived account", "address", acc.Address, "path", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case accounts.WalletOpened:
|
|
||||||
status, _ := event.Wallet.Status()
|
|
||||||
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
|
||||||
var derive = func(numToDerive int, base accounts.DerivationPath) {
|
|
||||||
// Derive first N accounts, hardcoded for now
|
|
||||||
var nextPath = make(accounts.DerivationPath, len(base))
|
|
||||||
copy(nextPath[:], base[:])
|
|
||||||
|
|
||||||
for i := 0; i < numToDerive; i++ {
|
|
||||||
acc, err := event.Wallet.Derive(nextPath, true)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Account derivation failed", "error", err)
|
|
||||||
} else {
|
|
||||||
log.Info("Derived account", "address", acc.Address, "path", nextPath)
|
|
||||||
}
|
|
||||||
nextPath[len(nextPath)-1]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if event.Wallet.URL().Scheme == "ledger" {
|
|
||||||
log.Info("Deriving ledger default paths")
|
|
||||||
derive(numberOfAccountsToDerive/2, accounts.DefaultBaseDerivationPath)
|
|
||||||
log.Info("Deriving ledger legacy paths")
|
|
||||||
derive(numberOfAccountsToDerive/2, accounts.LegacyLedgerBaseDerivationPath)
|
|
||||||
} else {
|
|
||||||
derive(numberOfAccountsToDerive, accounts.DefaultBaseDerivationPath)
|
|
||||||
}
|
|
||||||
case accounts.WalletDropped:
|
|
||||||
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
|
||||||
event.Wallet.Close()
|
|
||||||
}
|
}
|
||||||
|
log.Info("Deriving default paths")
|
||||||
|
derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.DefaultBaseDerivationPath))
|
||||||
|
if event.Wallet.URL().Scheme == "ledger" {
|
||||||
|
log.Info("Deriving ledger legacy paths")
|
||||||
|
derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.LegacyLedgerBaseDerivationPath))
|
||||||
|
log.Info("Deriving ledger live paths")
|
||||||
|
// For ledger live, since it's based off the same (DefaultBaseDerivationPath)
|
||||||
|
// as one we've already used, we need to step it forward one step to avoid
|
||||||
|
// hitting the same path again
|
||||||
|
nextFn := accounts.LedgerLiveIterator(accounts.DefaultBaseDerivationPath)
|
||||||
|
nextFn()
|
||||||
|
derive(numberOfAccountsToDerive, nextFn)
|
||||||
|
}
|
||||||
|
case accounts.WalletDropped:
|
||||||
|
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
||||||
|
event.Wallet.Close()
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns the set of wallet this signer manages. Each wallet can contain
|
// List returns the set of wallet this signer manages. Each wallet can contain
|
||||||
|
Loading…
Reference in New Issue
Block a user