Skip to content

Commit e99c788

Browse files
committed
accounts: ledger and HD review fixes
- Handle a data race where a Ledger drops between list and open - Prolong Ledger tx confirmation window to 30 days from 1 minute - Simplify Ledger chainid-signature calculation and validation - Simplify Ledger USB APDU request chunking algorithm - Silence keystore account cache notifications for manual actions - Only enable self derivations if wallet open succeeds
1 parent c7022c1 commit e99c788

File tree

5 files changed

+40
-61
lines changed

5 files changed

+40
-61
lines changed

accounts/keystore/account_cache.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,6 @@ func (ac *accountCache) add(newAccount accounts.Account) {
113113
copy(ac.all[i+1:], ac.all[i:])
114114
ac.all[i] = newAccount
115115
ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
116-
117-
select {
118-
case ac.notify <- struct{}{}:
119-
default:
120-
}
121116
}
122117

123118
// note: removed needs to be unique here (i.e. both File and Address must be set).
@@ -131,10 +126,6 @@ func (ac *accountCache) delete(removed accounts.Account) {
131126
} else {
132127
ac.byAddr[removed.Address] = ba
133128
}
134-
select {
135-
case ac.notify <- struct{}{}:
136-
default:
137-
}
138129
}
139130

140131
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {

accounts/keystore/account_cache_test.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func TestCacheInitialReload(t *testing.T) {
140140
}
141141

142142
func TestCacheAddDeleteOrder(t *testing.T) {
143-
cache, notify := newAccountCache("testdata/no-such-dir")
143+
cache, _ := newAccountCache("testdata/no-such-dir")
144144
cache.watcher.running = true // prevent unexpected reloads
145145

146146
accs := []accounts.Account{
@@ -176,20 +176,10 @@ func TestCacheAddDeleteOrder(t *testing.T) {
176176
for _, a := range accs {
177177
cache.add(a)
178178
}
179-
select {
180-
case <-notify:
181-
default:
182-
t.Fatalf("notifications didn't fire for adding new accounts")
183-
}
184179
// Add some of them twice to check that they don't get reinserted.
185180
cache.add(accs[0])
186181
cache.add(accs[2])
187182

188-
select {
189-
case <-notify:
190-
t.Fatalf("notifications fired for adding existing accounts")
191-
default:
192-
}
193183
// Check that the account list is sorted by filename.
194184
wantAccounts := make([]accounts.Account, len(accs))
195185
copy(wantAccounts, accs)
@@ -213,11 +203,6 @@ func TestCacheAddDeleteOrder(t *testing.T) {
213203
}
214204
cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
215205

216-
select {
217-
case <-notify:
218-
default:
219-
t.Fatalf("notifications didn't fire for deleting accounts")
220-
}
221206
// Check content again after deletion.
222207
wantAccountsAfterDelete := []accounts.Account{
223208
wantAccounts[1],

accounts/keystore/keystore_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func TestWalletNotifications(t *testing.T) {
286286

287287
// Randomly add and remove account and make sure events and wallets are in sync
288288
live := make(map[common.Address]accounts.Account)
289-
for i := 0; i < 256; i++ {
289+
for i := 0; i < 1024; i++ {
290290
// Execute a creation or deletion and ensure event arrival
291291
if create := len(live) == 0 || rand.Int()%4 > 0; create {
292292
// Add a new account and ensure wallet notifications arrives

accounts/usbwallet/ledger_wallet.go

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ func (w *ledgerWallet) Open(passphrase string) error {
192192
if err != nil {
193193
return err
194194
}
195+
if len(devices) == 0 {
196+
return accounts.ErrUnknownWallet
197+
}
195198
// Device opened, attach to the input and output endpoints
196199
device := devices[0]
197200

@@ -767,7 +770,7 @@ func (w *ledgerWallet) ledgerDerive(derivationPath []uint32) (common.Address, er
767770
func (w *ledgerWallet) ledgerSign(derivationPath []uint32, address common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
768771
// We need to modify the timeouts to account for user feedback
769772
defer func(old time.Duration) { w.device.ReadTimeout = old }(w.device.ReadTimeout)
770-
w.device.ReadTimeout = time.Minute
773+
w.device.ReadTimeout = time.Hour * 24 * 30 // Timeout requires a Ledger power cycle, only if you must
771774

772775
// Flatten the derivation path into the Ledger request
773776
path := make([]byte, 1+4*len(derivationPath))
@@ -823,7 +826,7 @@ func (w *ledgerWallet) ledgerSign(derivationPath []uint32, address common.Addres
823826
signer = new(types.HomesteadSigner)
824827
} else {
825828
signer = types.NewEIP155Signer(chainID)
826-
signature[64] = (signature[64]-34)/2 - byte(chainID.Uint64())
829+
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
827830
}
828831
// Inject the final signature into the transaction and sanity check the sender
829832
signed, err := tx.WithSignature(signer, signature)
@@ -875,45 +878,42 @@ func (w *ledgerWallet) ledgerSign(derivationPath []uint32, address common.Addres
875878
// Optional APDU data | arbitrary
876879
func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
877880
// Construct the message payload, possibly split into multiple chunks
878-
var chunks [][]byte
879-
for left := data; len(left) > 0 || len(chunks) == 0; {
880-
// Create the chunk header
881-
var chunk []byte
882-
883-
if len(chunks) == 0 {
884-
// The first chunk encodes the length and all the opcodes
885-
chunk = []byte{0x00, 0x00, 0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}
886-
binary.BigEndian.PutUint16(chunk, uint16(5+len(data)))
887-
}
888-
// Append the data blob to the end of the chunk
889-
space := 64 - len(chunk) - 5 // 5 == header size
890-
if len(left) > space {
891-
chunks, left = append(chunks, append(chunk, left[:space]...)), left[space:]
892-
continue
893-
}
894-
chunks, left = append(chunks, append(chunk, left...)), nil
895-
}
881+
apdu := make([]byte, 2, 7+len(data))
882+
883+
binary.BigEndian.PutUint16(apdu, uint16(5+len(data)))
884+
apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...)
885+
apdu = append(apdu, data...)
886+
896887
// Stream all the chunks to the device
897-
for i, chunk := range chunks {
898-
// Construct the new message to stream
899-
header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended
900-
binary.BigEndian.PutUint16(header[3:], uint16(i))
888+
header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended
889+
chunk := make([]byte, 64)
890+
space := len(chunk) - len(header)
901891

902-
msg := append(header, chunk...)
892+
for i := 0; len(apdu) > 0; i++ {
893+
// Construct the new message to stream
894+
chunk = append(chunk[:0], header...)
895+
binary.BigEndian.PutUint16(chunk[3:], uint16(i))
903896

897+
if len(apdu) > space {
898+
chunk = append(chunk, apdu[:space]...)
899+
apdu = apdu[space:]
900+
} else {
901+
chunk = append(chunk, apdu...)
902+
apdu = nil
903+
}
904904
// Send over to the device
905905
if glog.V(logger.Detail) {
906-
glog.Infof("-> %03d.%03d: %x", w.device.Bus, w.device.Address, msg)
906+
glog.Infof("-> %03d.%03d: %x", w.device.Bus, w.device.Address, chunk)
907907
}
908-
if _, err := w.input.Write(msg); err != nil {
908+
if _, err := w.input.Write(chunk); err != nil {
909909
return nil, err
910910
}
911911
}
912912
// Stream the reply back from the wallet in 64 byte chunks
913913
var reply []byte
914+
chunk = chunk[:64] // Yeah, we surely have enough space
914915
for {
915916
// Read the next chunk from the Ledger wallet
916-
chunk := make([]byte, 64)
917917
if _, err := io.ReadFull(w.output, chunk); err != nil {
918918
return nil, err
919919
}
@@ -925,17 +925,19 @@ func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l
925925
return nil, errReplyInvalidHeader
926926
}
927927
// If it's the first chunk, retrieve the total message length
928+
var payload []byte
929+
928930
if chunk[3] == 0x00 && chunk[4] == 0x00 {
929931
reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7])))
930-
chunk = chunk[7:]
932+
payload = chunk[7:]
931933
} else {
932-
chunk = chunk[5:]
934+
payload = chunk[5:]
933935
}
934936
// Append to the reply and stop when filled up
935-
if left := cap(reply) - len(reply); left > len(chunk) {
936-
reply = append(reply, chunk...)
937+
if left := cap(reply) - len(reply); left > len(payload) {
938+
reply = append(reply, payload...)
937939
} else {
938-
reply = append(reply, chunk[:left]...)
940+
reply = append(reply, payload[:left]...)
939941
break
940942
}
941943
}

cmd/geth/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,9 @@ func startNode(ctx *cli.Context, stack *node.Node) {
273273
for _, wallet := range stack.AccountManager().Wallets() {
274274
if err := wallet.Open(""); err != nil {
275275
glog.V(logger.Warn).Infof("Failed to open wallet %s: %v", wallet.URL(), err)
276+
} else {
277+
wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
276278
}
277-
wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
278279
}
279280
// Listen for wallet event till termination
280281
for event := range events {
@@ -283,8 +284,8 @@ func startNode(ctx *cli.Context, stack *node.Node) {
283284
glog.V(logger.Info).Infof("New wallet appeared: %s, failed to open: %s", event.Wallet.URL(), err)
284285
} else {
285286
glog.V(logger.Info).Infof("New wallet appeared: %s, %s", event.Wallet.URL(), event.Wallet.Status())
287+
event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
286288
}
287-
event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
288289
} else {
289290
glog.V(logger.Info).Infof("Old wallet dropped: %s", event.Wallet.URL())
290291
event.Wallet.Close()

0 commit comments

Comments
 (0)